Skip to content

Commit

Permalink
Added mojang auth proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Fallen-Breath committed Oct 28, 2023
1 parent cc3822f commit 54634a7
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 10 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
A custom fork of [Velocity](https://github.com/PaperMC/Velocity)

A list of small tweaks I made:

- Added proxy setting for authenticating player with `sessionserver.mojang.com`
- The proxy setting is in the `auth-proxy` section in `velocity.toml`, of course you know how to fill it
- Supported proxy types: `socks4`, `socks5`, `http`
- Due to [an issue](https://github.com/AsyncHttpClient/async-http-client/issues/1913) in the http library velocity uses, you can only use `http` proxy for now
- If enabled, velocity will firstly try authenticating with the given proxy, if failed it will try again without the proxy

# Velocity

[![Build Status](https://img.shields.io/github/actions/workflow/status/PaperMC/Velocity/gradle.yml)](https://papermc.io/downloads/velocity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ void start() {
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
}

// [fallen's fork] mojang auth proxy: create the proxied http client
cm.createProxiedHttpClient();

Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
}

Expand Down Expand Up @@ -581,6 +584,11 @@ public AsyncHttpClient getAsyncHttpClient() {
return cm.getHttpClient();
}

// [fallen's fork] mojang auth proxy
public AsyncHttpClient getAsyncProxiedHttpClient() {
return cm.getProxiedHttpClient();
}

public Ratelimiter getIpAttemptLimiter() {
return ipAttemptLimiter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ public class VelocityConfiguration implements ProxyConfig {
private final ForcedHosts forcedHosts;
@Expose
private final Advanced advanced;

// [fallen's fork] mojang auth proxy
@Expose
private final AuthProxy authProxy;

@Expose
private final Query query;
private final Metrics metrics;
Expand All @@ -90,10 +95,12 @@ public class VelocityConfiguration implements ProxyConfig {
private boolean forceKeyAuthentication = true; // Added in 1.19

private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
AuthProxy authProxy, // [fallen's fork] mojang auth proxy
Query query, Metrics metrics) {
this.servers = servers;
this.forcedHosts = forcedHosts;
this.advanced = advanced;
this.authProxy = authProxy; // [fallen's fork] mojang auth proxy
this.query = query;
this.metrics = metrics;
}
Expand All @@ -103,7 +110,9 @@ private VelocityConfiguration(String bind, String motd, int showMaxPlayers, bool
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
boolean enablePlayerAddressLogging, Servers servers, ForcedHosts forcedHosts,
Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication) {
Advanced advanced,
AuthProxy authProxy, // [fallen's fork] mojang auth proxy
Query query, Metrics metrics, boolean forceKeyAuthentication) {
this.bind = bind;
this.motd = motd;
this.showMaxPlayers = showMaxPlayers;
Expand All @@ -118,6 +127,7 @@ private VelocityConfiguration(String bind, String motd, int showMaxPlayers, bool
this.servers = servers;
this.forcedHosts = forcedHosts;
this.advanced = advanced;
this.authProxy = authProxy; // [fallen's fork] mojang auth proxy
this.query = query;
this.metrics = metrics;
this.forceKeyAuthentication = forceKeyAuthentication;
Expand Down Expand Up @@ -392,6 +402,24 @@ public boolean isLogPlayerConnections() {
return advanced.isLogPlayerConnections();
}

// [fallen's fork] mojang auth proxy starts
public boolean isAuthProxyEnabled() {
return authProxy.isEnabled();
}

public String getAuthProxyType() {
return authProxy.getType();
}

public String getAuthProxyHostname() {
return authProxy.getHostname();
}

public int getAuthProxyPort() {
return authProxy.getPort();
}
// [fallen's fork] mojang auth proxy ends

public boolean isForceKeyAuthentication() {
return forceKeyAuthentication;
}
Expand Down Expand Up @@ -548,6 +576,7 @@ public static VelocityConfiguration read(Path path) throws IOException {
CommentedConfig serversConfig = config.get("servers");
CommentedConfig forcedHostsConfig = config.get("forced-hosts");
CommentedConfig advancedConfig = config.get("advanced");
CommentedConfig autoProxy = config.get("auth-proxy");
CommentedConfig queryConfig = config.get("query");
CommentedConfig metricsConfig = config.get("metrics");
PlayerInfoForwarding forwardingMode = config.getEnumOrElse("player-info-forwarding-mode",
Expand Down Expand Up @@ -588,6 +617,7 @@ public static VelocityConfiguration read(Path path) throws IOException {
new Servers(serversConfig),
new ForcedHosts(forcedHostsConfig),
new Advanced(advancedConfig),
new AuthProxy(autoProxy),
new Query(queryConfig),
new Metrics(metricsConfig),
forceKeyAuthentication
Expand Down Expand Up @@ -857,6 +887,45 @@ public String toString() {
}
}

/**
* [fallen's fork] mojang auth proxy.
*/
private static class AuthProxy {
@Expose
private boolean enabled = false;
@Expose
private String type = "http";
@Expose
private String hostname = "127.0.0.1";
@Expose
private int port = 1081;

public AuthProxy(CommentedConfig config) {
if (config != null) {
this.enabled = config.getOrElse("enabled", false);
this.type = config.getOrElse("type", "http");
this.hostname = config.getOrElse("hostname", "127.0.0.1");
this.port = config.getIntOrElse("port", 1081);
}
}

public boolean isEnabled() {
return enabled;
}

public String getType() {
return type;
}

public String getHostname() {
return hostname;
}

public int getPort() {
return port;
}
}

private static class Query {

@Expose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

Expand Down Expand Up @@ -209,9 +209,11 @@ public boolean handle(EncryptionResponse packet) {
url += "&ip=" + urlFormParameterEscaper().escape(playerIp);
}

ListenableFuture<Response> hasJoinedResponse = server.getAsyncHttpClient().prepareGet(url)
.execute();
hasJoinedResponse.addListener(() -> {
// [fallen's fork] mojang auth proxy: change the callback logics to make it reusable
var responseHolder = new Object() {
Future<Response> responseFuture;
};
Runnable requestDoneListener = () -> {
if (mcConnection.isClosed()) {
// The player disconnected after we authenticated them.
return;
Expand All @@ -230,7 +232,9 @@ public boolean handle(EncryptionResponse packet) {
}

try {
Response profileResponse = hasJoinedResponse.get();
// [fallen's fork] mojang auth proxy: tweak the future used
Response profileResponse = responseHolder.responseFuture.get();

if (profileResponse.getStatusCode() == 200) {
final GameProfile profile = GENERAL_GSON.fromJson(profileResponse.getResponseBody(),
GameProfile.class);
Expand Down Expand Up @@ -265,7 +269,45 @@ public boolean handle(EncryptionResponse packet) {
// not much we can do usefully
Thread.currentThread().interrupt();
}
}, mcConnection.eventLoop());
};

// [fallen's fork] mojang auth proxy starts
var finalUrl = url;
Runnable directAuth = () -> {
var directFuture = server.getAsyncHttpClient().prepareGet(finalUrl).execute();
responseHolder.responseFuture = directFuture;
directFuture.addListener(requestDoneListener, mcConnection.eventLoop());
};
var proxiedClient = server.getAsyncProxiedHttpClient();
if (proxiedClient != null) {
var proxiedFuture = proxiedClient.prepareGet(url).execute();
proxiedFuture.addListener(() -> {
try {
Response profileResponse = proxiedFuture.get();
if (profileResponse.getStatusCode() == 200) {
// proxied auth ok
responseHolder.responseFuture = proxiedFuture;
requestDoneListener.run();
return;
} else {
// proxied auth failed
logger.error("Error authenticating with proxy, http status code {}, try without",
profileResponse.getStatusCode());
}
} catch (ExecutionException e) {
logger.error("Error authenticating with proxy, try without", e);
} catch (InterruptedException e) {
// not much we can do usefully
Thread.currentThread().interrupt();
}
// it's fine to authenticate failed with proxy, let's try again without it
directAuth.run();
}, mcConnection.eventLoop());
} else {
directAuth.run();
}
// [fallen's fork] mojang auth proxy ends

} catch (GeneralSecurityException e) {
logger.error("Unable to enable encryption", e);
mcConnection.close(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.RequestBuilder;
import org.asynchttpclient.filter.FilterContext;
import org.asynchttpclient.filter.FilterContext.FilterContextBuilder;
import org.asynchttpclient.filter.RequestFilter;
import org.asynchttpclient.proxy.ProxyServer;
import org.asynchttpclient.proxy.ProxyType;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
Expand All @@ -71,6 +74,11 @@ public final class ConnectionManager {
private final SeparatePoolInetNameResolver resolver;
private final AsyncHttpClient httpClient;

// [fallen's fork] mojang auth proxy starts
private AsyncHttpClient proxiedHttpClient;
private final Supplier<AsyncHttpClient> proxiedHttpClientSupplier;
// [fallen's fork] mojang auth proxy ends

/**
* Initalizes the {@code ConnectionManager}.
*
Expand All @@ -86,7 +94,8 @@ public ConnectionManager(VelocityServer server) {
this.backendChannelInitializer = new BackendChannelInitializerHolder(
new BackendChannelInitializer(this.server));
this.resolver = new SeparatePoolInetNameResolver(GlobalEventExecutor.INSTANCE);
this.httpClient = asyncHttpClient(config()
// [fallen's fork] mojang auth proxy: reuse the builder
var builder = config()
.setEventLoopGroup(this.workerGroup)
.setUserAgent(server.getVersion().getName() + "/" + server.getVersion().getVersion())
.addRequestFilter(new RequestFilter() {
Expand All @@ -98,8 +107,44 @@ public <T> FilterContext<T> filter(FilterContext<T> ctx) {
.build())
.build();
}
})
.build());
});
this.httpClient = asyncHttpClient(builder.build());

// [fallen's fork] mojang auth proxy: create the proxied client
// it needs to be lazy-initialized,
// cuz the server.getConfiguration() is not available yet in the constructor
this.proxiedHttpClient = null;
this.proxiedHttpClientSupplier = () -> {
if (server.getConfiguration().isAuthProxyEnabled()) {
ProxyType proxyType = null;
String type = server.getConfiguration().getAuthProxyType();
switch (type) {
case "socks4":
proxyType = ProxyType.SOCKS_V4;
break;
case "socks5":
proxyType = ProxyType.SOCKS_V5;
break;
case "http":
proxyType = ProxyType.HTTP;
break;
default:
LOGGER.error("Bad auth proxy type {}", type);
}
if (proxyType != ProxyType.HTTP) {
LOGGER.warn("Only http proxy is supported. See readme for more information");
proxyType = null;
}
if (proxyType != null) {
var hostname = server.getConfiguration().getAuthProxyHostname();
var port = server.getConfiguration().getAuthProxyPort();
var proxyServer = new ProxyServer.Builder(hostname, port).setProxyType(proxyType).build();
LOGGER.info("Mojang authorization proxy enabled, using {}://{}:{}", type, hostname, port);
return asyncHttpClient(builder.setProxyServer(proxyServer).build());
}
}
return null;
};
}

public void logChannelInformation() {
Expand Down Expand Up @@ -260,6 +305,16 @@ public AsyncHttpClient getHttpClient() {
return httpClient;
}

// [fallen's fork] mojang auth proxy starts
public AsyncHttpClient getProxiedHttpClient() {
return proxiedHttpClient;
}

public void createProxiedHttpClient() {
proxiedHttpClient = proxiedHttpClientSupplier.get();
}
// [fallen's fork] mojang auth proxy ends

public BackendChannelInitializerHolder getBackendChannelInitializer() {
return this.backendChannelInitializer;
}
Expand Down
8 changes: 8 additions & 0 deletions proxy/src/main/resources/default-velocity.toml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ log-command-executions = false
# and disconnecting from the proxy.
log-player-connections = true

# [fallen's fork] mojang auth proxy
# See readme for more information
[auth-proxy]
enabled = false
type = "http"
hostname = "127.0.0.1"
port = 1081

[query]
# Whether to enable responding to GameSpy 4 query responses or not.
enabled = false
Expand Down

0 comments on commit 54634a7

Please sign in to comment.