Skip to content

Commit adc537a

Browse files
author
ssquadteam
committed
Large Dependency Bump + Velocity Exploit Fix
1 parent 8e82509 commit adc537a

File tree

11 files changed

+183
-59
lines changed

11 files changed

+183
-59
lines changed

.DS_Store

0 Bytes
Binary file not shown.

gradle/libs.versions.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.1
2020
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
2121
bstats = "org.bstats:bstats-base:3.1.0"
2222
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.0"
23-
checker-qual = "org.checkerframework:checker-qual:3.49.0"
24-
checkstyle = "com.puppycrawl.tools:checkstyle:10.21.3"
23+
checker-qual = "org.checkerframework:checker-qual:3.49.1"
24+
checkstyle = "com.puppycrawl.tools:checkstyle:10.21.4"
2525
completablefutures = "com.spotify:completable-futures:0.3.6"
2626
configurate3-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate3" }
2727
configurate3-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate3" }
@@ -48,7 +48,7 @@ log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "lo
4848
log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" }
4949
log4j-iostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version.ref = "log4j" }
5050
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
51-
mockito = "org.mockito:mockito-core:5.15.2"
51+
mockito = "org.mockito:mockito-core:5.16.0"
5252
netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" }
5353
netty-codec-haproxy = { module = "io.netty:netty-codec-haproxy", version.ref = "netty" }
5454
netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
@@ -58,7 +58,7 @@ netty-transport-native-kqueue = { module = "io.netty:netty-transport-native-kque
5858
nightconfig = "com.electronwill.night-config:toml:3.8.1"
5959
slf4j = "org.slf4j:slf4j-api:2.0.17"
6060
snakeyaml = "org.yaml:snakeyaml:1.33"
61-
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.9.1"
61+
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.9.2"
6262
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"
6363

6464
[bundles]

proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -507,15 +507,15 @@ public int run(final CommandContext<CommandSource> context) {
507507
if (version.getName().equals("Velocity")) {
508508
final TextComponent embellishment = Component.text()
509509
.append(Component.text()
510-
.content("discord.gg/beer")
511-
.color(NamedTextColor.GOLD)
510+
.content("discord.gg/themegahivemc")
511+
.color(NamedTextColor.RED)
512512
.clickEvent(
513-
ClickEvent.openUrl("https://discord.gg/themegahive"))
513+
ClickEvent.openUrl("https://discord.gg/themegahivemc"))
514514
.build())
515515
.append(Component.text(" - "))
516516
.append(Component.text()
517517
.content("GitHub")
518-
.color(NamedTextColor.GOLD)
518+
.color(NamedTextColor.RED)
519519
.decoration(TextDecoration.UNDERLINED, true)
520520
.clickEvent(ClickEvent.openUrl(
521521
"https://github.com/ssquadteam/ApiaryProxy"))

proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ public final class VelocityConfiguration implements ProxyConfig {
9696
private final Query query;
9797
private final Metrics metrics;
9898
@Expose
99+
private int maxCommandsPerSecond = 10;
100+
@Expose
99101
private final Redis redis;
100102
@Expose
101103
private final Queue queue;
@@ -151,10 +153,11 @@ private VelocityConfiguration(final String bind, final String motd, final List<S
151153
final boolean enablePlayerAddressLogging, final Servers servers, final ForcedHosts forcedHosts,
152154
final Commands commands, final Advanced advanced, final Query query, final Metrics metrics,
153155
final boolean forceKeyAuthentication, final boolean logPlayerConnections, final boolean logPlayerDisconnections,
154-
final boolean logOfflineConnections, final boolean disableForge, final boolean enforceChatSigning,
155-
final boolean translateHeaderFooter, final boolean logMinimumVersion, final String minimumVersion, final Redis redis,
156-
final Queue queue, final Map<String, List<String>> slashServers, final List<ServerLink> serverLinks,
157-
final List<ProxyAddress> proxyAddresses, final String dynamicProxyFilter, final Map<String, Integer> playerCaps) {
156+
final boolean logOfflineConnections, final int maxCommandsPerSecond, final boolean disableForge,
157+
final boolean enforceChatSigning, final boolean translateHeaderFooter, final boolean logMinimumVersion,
158+
final String minimumVersion, final Redis redis, final Queue queue, final Map<String, List<String>> slashServers,
159+
final List<ServerLink> serverLinks, final List<ProxyAddress> proxyAddresses, final String dynamicProxyFilter,
160+
final Map<String, Integer> playerCaps) {
158161
this.bind = bind;
159162
this.motd = motd;
160163
this.motdHover = motdHover;
@@ -176,6 +179,7 @@ private VelocityConfiguration(final String bind, final String motd, final List<S
176179
this.forceKeyAuthentication = forceKeyAuthentication;
177180
this.logPlayerConnections = logPlayerConnections;
178181
this.logPlayerDisconnections = logPlayerDisconnections;
182+
this.maxCommandsPerSecond = maxCommandsPerSecond;
179183
this.logOfflineConnections = logOfflineConnections;
180184
this.disableForge = disableForge;
181185
this.enforceChatSigning = enforceChatSigning;
@@ -437,6 +441,10 @@ public Map<String, List<String>> getForcedHosts() {
437441
return forcedHosts.getForcedHosts();
438442
}
439443

444+
public long getMaxCommandsPerSecond() {
445+
return maxCommandsPerSecond;
446+
}
447+
440448
@Override
441449
public int getCompressionThreshold() {
442450
return advanced.getCompressionThreshold();
@@ -596,6 +604,10 @@ public boolean isForceKeyAuthentication() {
596604
return forceKeyAuthentication;
597605
}
598606

607+
public boolean isEnableReusePort() {
608+
return advanced.isEnableReusePort();
609+
}
610+
599611
public @NotNull Redis getRedis() {
600612
return redis;
601613
}
@@ -751,6 +763,8 @@ public static VelocityConfiguration read(final Path path) throws IOException {
751763
final boolean kickExisting = config.getOrElse("kick-existing-players", false);
752764
final boolean enablePlayerAddressLogging = config.getOrElse(
753765
"enable-player-address-logging", true);
766+
final int maxCommandsPerSecond = config.getOrElse(
767+
"max-commands-per-second", 10);
754768
final boolean logPlayerConnections = config.getOrElse(
755769
"log-player-connections", true);
756770
final boolean logPlayerDisconnections = config.getOrElse(
@@ -850,6 +864,7 @@ public static VelocityConfiguration read(final Path path) throws IOException {
850864
logPlayerConnections,
851865
logPlayerDisconnections,
852866
logOfflineConnections,
867+
maxCommandsPerSecond,
853868
disableForge,
854869
enforceChatSigning,
855870
translateHeaderFooter,
@@ -1222,6 +1237,8 @@ private static final class Advanced {
12221237
@Expose
12231238
private boolean acceptTransfers = false;
12241239
@Expose
1240+
private boolean enableReusePort = false;
1241+
@Expose
12251242
private boolean allowIllegalCharactersInChat = false;
12261243
@Expose
12271244
private String serverBrand = "{backend-brand} ({proxy-brand})";
@@ -1259,6 +1276,7 @@ private Advanced(final CommentedConfig config) {
12591276
this.announceProxyCommands = config.getOrElse("announce-proxy-commands", true);
12601277
this.logCommandExecutions = config.getOrElse("log-command-executions", false);
12611278
this.acceptTransfers = config.getOrElse("accepts-transfers", false);
1279+
this.enableReusePort = config.getOrElse("enable-reuse-port", false);
12621280
this.allowIllegalCharactersInChat = config.getOrElse("allow-illegal-characters-in-chat", false);
12631281
this.serverBrand = config.getOrElse("server-brand", "{backend-brand} ({proxy-brand})");
12641282
this.fallbackVersionPing = config.getOrElse("fallback-version-ping", "{proxy-brand} {protocol-min}-{protocol-max}");
@@ -1330,6 +1348,10 @@ public boolean isAcceptTransfers() {
13301348
return this.acceptTransfers;
13311349
}
13321350

1351+
public boolean isEnableReusePort() {
1352+
return enableReusePort;
1353+
}
1354+
13331355
public boolean isAllowIllegalCharactersInChat() {
13341356
return allowIllegalCharactersInChat;
13351357
}
@@ -1370,6 +1392,7 @@ public String toString() {
13701392
+ ", announceProxyCommands=" + announceProxyCommands
13711393
+ ", logCommandExecutions=" + logCommandExecutions
13721394
+ ", acceptTransfers=" + acceptTransfers
1395+
+ ", enableReusePort=" + enableReusePort
13731396
+ ", allowIllegalCharactersInChat=" + allowIllegalCharactersInChat
13741397
+ '}';
13751398
}

proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java

Lines changed: 78 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
package com.velocitypowered.proxy.network;
1919

2020
import com.google.common.base.Preconditions;
21+
import com.google.common.collect.HashMultimap;
22+
import com.google.common.collect.Multimap;
2123
import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
2224
import com.velocitypowered.api.event.proxy.ListenerCloseEvent;
2325
import com.velocitypowered.api.network.ListenerType;
@@ -28,14 +30,17 @@
2830
import io.netty.bootstrap.Bootstrap;
2931
import io.netty.bootstrap.ServerBootstrap;
3032
import io.netty.channel.Channel;
33+
import io.netty.channel.ChannelFuture;
3134
import io.netty.channel.ChannelFutureListener;
3235
import io.netty.channel.ChannelOption;
3336
import io.netty.channel.EventLoopGroup;
3437
import io.netty.channel.WriteBufferWaterMark;
38+
import io.netty.channel.unix.UnixChannelOption;
3539
import io.netty.util.concurrent.GlobalEventExecutor;
40+
import io.netty.util.concurrent.MultithreadEventExecutorGroup;
3641
import java.net.InetSocketAddress;
3742
import java.net.http.HttpClient;
38-
import java.util.HashMap;
43+
import java.util.Collection;
3944
import java.util.Map;
4045
import org.apache.logging.log4j.LogManager;
4146
import org.apache.logging.log4j.Logger;
@@ -50,7 +55,7 @@ public final class ConnectionManager {
5055
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
5156
1 << 21);
5257
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class, new ParameterizedMessageFactory());
53-
private final Map<InetSocketAddress, Endpoint> endpoints = new HashMap<>();
58+
private final Multimap<InetSocketAddress, Endpoint> endpoints = HashMultimap.create();
5459
private final TransportType transportType;
5560
private final EventLoopGroup bossGroup;
5661
private final EventLoopGroup workerGroup;
@@ -92,7 +97,6 @@ public void logChannelInformation() {
9297
public void bind(final InetSocketAddress address) {
9398
final ServerBootstrap bootstrap = new ServerBootstrap()
9499
.channelFactory(this.transportType.serverSocketChannelFactory)
95-
.group(this.bossGroup, this.workerGroup)
96100
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
97101
.childHandler(this.serverChannelInitializer.get())
98102
.childOption(ChannelOption.TCP_NODELAY, true)
@@ -103,26 +107,50 @@ public void bind(final InetSocketAddress address) {
103107
bootstrap.option(ChannelOption.TCP_FASTOPEN, 3);
104108
}
105109

106-
bootstrap.bind()
107-
.addListener((ChannelFutureListener) future -> {
108-
final Channel channel = future.channel();
109-
if (future.isSuccess()) {
110-
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
111-
112-
// Warn people with console access that HAProxy is in use, see PR: #1436
113-
if (this.server.getConfiguration().isProxyProtocol()) {
114-
LOGGER.warn("Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.", channel.localAddress());
115-
}
110+
if (server.getConfiguration().isEnableReusePort()) {
111+
// We don't need a boss group, since each worker will bind to the socket
112+
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true)
113+
.group(this.workerGroup);
114+
} else {
115+
bootstrap.group(this.bossGroup, this.workerGroup);
116+
}
116117

117-
LOGGER.info("Listening on {}", channel.localAddress());
118+
final int binds = server.getConfiguration().isEnableReusePort()
119+
? ((MultithreadEventExecutorGroup) this.workerGroup).executorCount() : 1;
118120

119-
// Fire the proxy bound event after the socket is bound
120-
server.getEventManager().fireAndForget(
121-
new ListenerBoundEvent(address, ListenerType.MINECRAFT));
122-
} else {
123-
LOGGER.error("Can't bind to {}", address, future.cause());
124-
}
125-
});
121+
for (int bind = 0; bind < binds; bind++) {
122+
// Wait for each bind to open. If we encounter any errors, don't try to bind again.
123+
int finalBind = bind;
124+
ChannelFuture f = bootstrap.bind()
125+
.addListener((ChannelFutureListener) future -> {
126+
final Channel channel = future.channel();
127+
if (future.isSuccess()) {
128+
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
129+
130+
LOGGER.info("Listening on {}", channel.localAddress());
131+
132+
if (finalBind == 0) {
133+
// Warn people with console access that HAProxy is in use, see PR: #1436
134+
if (this.server.getConfiguration().isProxyProtocol()) {
135+
LOGGER.warn(
136+
"Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.",
137+
channel.localAddress());
138+
}
139+
140+
// Fire the proxy bound event after the socket is bound
141+
server.getEventManager().fireAndForget(
142+
new ListenerBoundEvent(address, ListenerType.MINECRAFT));
143+
}
144+
} else {
145+
LOGGER.error("Can't bind to {}", address, future.cause());
146+
}
147+
});
148+
f.syncUninterruptibly();
149+
150+
if (!f.isSuccess()) {
151+
break;
152+
}
153+
}
126154
}
127155

128156
/**
@@ -180,17 +208,20 @@ public Bootstrap createWorker(@Nullable final EventLoopGroup group) {
180208
* @param oldBind the endpoint to close
181209
*/
182210
public void close(final InetSocketAddress oldBind) {
183-
Endpoint endpoint = endpoints.remove(oldBind);
211+
Collection<Endpoint> endpoints = this.endpoints.removeAll(oldBind);
212+
Preconditions.checkState(!endpoints.isEmpty(), "Endpoint was not registered");
213+
214+
ListenerType type = endpoints.iterator().next().type();
184215

185216
// Fire proxy close event to notify plugins of socket close. We block since plugins
186217
// should have a chance to be notified before the server stops accepting connections.
187-
server.getEventManager().fire(new ListenerCloseEvent(oldBind, endpoint.type())).join();
188-
189-
Channel serverChannel = endpoint.channel();
218+
server.getEventManager().fire(new ListenerCloseEvent(oldBind, type)).join();
190219

191-
Preconditions.checkState(serverChannel != null, "Endpoint %s not registered", oldBind);
192-
LOGGER.info("Closing endpoint {}", serverChannel.localAddress());
193-
serverChannel.close().syncUninterruptibly();
220+
for (Endpoint endpoint : endpoints) {
221+
Channel serverChannel = endpoint.channel();
222+
LOGGER.info("Closing endpoint {}", serverChannel.localAddress());
223+
serverChannel.close().syncUninterruptibly();
224+
}
194225
}
195226

196227
/**
@@ -199,24 +230,28 @@ public void close(final InetSocketAddress oldBind) {
199230
* @param interrupt should closing forward interruptions
200231
*/
201232
public void closeEndpoints(final boolean interrupt) {
202-
for (final Map.Entry<InetSocketAddress, Endpoint> entry : this.endpoints.entrySet()) {
233+
for (final Map.Entry<InetSocketAddress, Collection<Endpoint>> entry : this.endpoints.asMap()
234+
.entrySet()) {
203235
final InetSocketAddress address = entry.getKey();
204-
final Endpoint endpoint = entry.getValue();
236+
final Collection<Endpoint> endpoints = entry.getValue();
237+
ListenerType type = endpoints.iterator().next().type();
205238

206239
// Fire proxy close event to notify plugins of socket close. We block since plugins
207240
// should have a chance to be notified before the server stops accepting connections.
208-
server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.type())).join();
209-
210-
LOGGER.info("Closing endpoint {}", address);
211-
if (interrupt) {
212-
try {
213-
endpoint.channel().close().sync();
214-
} catch (final InterruptedException e) {
215-
LOGGER.info("Interrupted whilst closing endpoint", e);
216-
Thread.currentThread().interrupt();
241+
server.getEventManager().fire(new ListenerCloseEvent(address, type)).join();
242+
243+
for (Endpoint endpoint : endpoints) {
244+
LOGGER.info("Closing endpoint {}", address);
245+
if (interrupt) {
246+
try {
247+
endpoint.channel().close().sync();
248+
} catch (final InterruptedException e) {
249+
LOGGER.info("Interrupted whilst closing endpoint", e);
250+
Thread.currentThread().interrupt();
251+
}
252+
} else {
253+
endpoint.channel().close().syncUninterruptibly();
217254
}
218-
} else {
219-
endpoint.channel().close().syncUninterruptibly();
220255
}
221256
}
222257
this.endpoints.clear();
@@ -246,8 +281,8 @@ public ServerChannelInitializerHolder getServerChannelInitializer() {
246281
*/
247282
public HttpClient createHttpClient() {
248283
return HttpClient.newBuilder()
249-
.executor(this.workerGroup)
250-
.build();
284+
.executor(this.workerGroup)
285+
.build();
251286
}
252287

253288
public BackendChannelInitializerHolder getBackendChannelInitializer() {

0 commit comments

Comments
 (0)