From 771dfa8d0ebef07b8da765fa7ff7aea4c504e8db Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 22 Oct 2020 02:01:29 -0400 Subject: [PATCH] Cherry-pick full Unix domain socket support from b00389029f4c11ad34898b5298625f4fcfc03de5 Closes #991. We can't properly support this without an API break, so let's just do it in 5.0.0. --- .../api/event/proxy/ListenerBoundEvent.java | 8 +-- .../api/event/proxy/ListenerCloseEvent.java | 8 +-- .../api/proxy/InboundConnection.java | 7 +-- .../com/velocitypowered/api/proxy/Player.java | 1 - .../api/proxy/ProxyServer.java | 4 +- .../api/proxy/server/ServerInfo.java | 8 +-- .../velocitypowered/proxy/VelocityServer.java | 30 ++++++----- .../proxy/config/VelocityConfiguration.java | 4 +- .../backend/BungeeCordMessageResponder.java | 37 ++++++++++--- .../backend/VelocityServerConnection.java | 52 ++++++++++++------- .../connection/client/ConnectedPlayer.java | 5 +- .../proxy/network/ConnectionManager.java | 30 ++++++----- .../proxy/network/TransportType.java | 52 +++++++++++++++++++ .../protocol/netty/GameSpyQueryHandler.java | 5 +- .../proxy/server/PingSessionHandler.java | 14 ++++- .../server/VelocityRegisteredServer.java | 2 +- .../proxy/util/AddressUtil.java | 22 +++++--- .../proxy/util/InformationUtils.java | 18 ++++--- 18 files changed, 218 insertions(+), 89 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerBoundEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerBoundEvent.java index 662e403db..b735bd810 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerBoundEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerBoundEvent.java @@ -9,22 +9,22 @@ package com.velocitypowered.api.event.proxy; import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ListenerType; -import java.net.InetSocketAddress; +import java.net.SocketAddress; /** * This event is fired by the proxy after a listener starts accepting connections. */ public final class ListenerBoundEvent { - private final InetSocketAddress address; + private final SocketAddress address; private final ListenerType listenerType; - public ListenerBoundEvent(InetSocketAddress address, ListenerType listenerType) { + public ListenerBoundEvent(SocketAddress address, ListenerType listenerType) { this.address = Preconditions.checkNotNull(address, "address"); this.listenerType = Preconditions.checkNotNull(listenerType, "listenerType"); } - public InetSocketAddress getAddress() { + public SocketAddress getAddress() { return address; } diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerCloseEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerCloseEvent.java index c2551a359..edb01d126 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerCloseEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerCloseEvent.java @@ -9,22 +9,22 @@ package com.velocitypowered.api.event.proxy; import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ListenerType; -import java.net.InetSocketAddress; +import java.net.SocketAddress; /** * This event is fired by the proxy before the proxy stops accepting connections. */ public final class ListenerCloseEvent { - private final InetSocketAddress address; + private final SocketAddress address; private final ListenerType listenerType; - public ListenerCloseEvent(InetSocketAddress address, ListenerType listenerType) { + public ListenerCloseEvent(SocketAddress address, ListenerType listenerType) { this.address = Preconditions.checkNotNull(address, "address"); this.listenerType = Preconditions.checkNotNull(listenerType, "listenerType"); } - public InetSocketAddress getAddress() { + public SocketAddress getAddress() { return address; } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java index e92c38bc7..0ef4ee7c3 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java @@ -9,6 +9,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.network.ProtocolVersion; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Optional; /** @@ -17,11 +18,11 @@ import java.util.Optional; public interface InboundConnection { /** - * Returns the player's IP address. + * Returns the player's remote address. * - * @return the player's IP + * @return the player's remote address */ - InetSocketAddress getRemoteAddress(); + SocketAddress getRemoteAddress(); /** * Returns the hostname that the user entered into the client, if applicable. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index cd41d8468..c17dbeee7 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -31,7 +31,6 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.Nullable; import org.jetbrains.annotations.NotNull; /** diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java index 8f4ec2f65..dca3182ab 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -18,7 +18,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.util.ProxyVersion; -import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Collection; import java.util.Optional; import java.util.UUID; @@ -179,7 +179,7 @@ public interface ProxyServer extends Audience { * * @return the address the proxy is bound to */ - InetSocketAddress getBoundAddress(); + SocketAddress getBoundAddress(); /** * Gets the {@link ProxyConfig} instance. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java index fd686297e..fa8c7c857 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java @@ -8,7 +8,7 @@ package com.velocitypowered.api.proxy.server; import com.google.common.base.Preconditions; -import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; @@ -19,7 +19,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class ServerInfo implements Comparable { private final String name; - private final InetSocketAddress address; + private final SocketAddress address; /** * Creates a new ServerInfo object. @@ -27,7 +27,7 @@ public final class ServerInfo implements Comparable { * @param name the name for the server * @param address the address of the server to connect to */ - public ServerInfo(String name, InetSocketAddress address) { + public ServerInfo(String name, SocketAddress address) { this.name = Preconditions.checkNotNull(name, "name"); this.address = Preconditions.checkNotNull(address, "address"); } @@ -36,7 +36,7 @@ public final class ServerInfo implements Comparable { return name; } - public final InetSocketAddress getAddress() { + public final SocketAddress getAddress() { return address; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 56efaf73d..0c3ffd6bb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -70,6 +70,7 @@ import io.netty.channel.EventLoopGroup; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyPair; @@ -231,16 +232,18 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { // init console permissions after plugins are loaded console.setupPermissions(); + final SocketAddress bindAddr = configuration.getBind(); final Integer port = this.options.getPort(); - if (port != null) { + if (port != null && bindAddr instanceof InetSocketAddress) { logger.debug("Overriding bind port to {} from command line option", port); - this.cm.bind(new InetSocketAddress(configuration.getBind().getHostString(), port)); + this.cm.bind(new InetSocketAddress(((InetSocketAddress) bindAddr).getHostString(), port)); } else { this.cm.bind(configuration.getBind()); } - if (configuration.isQueryEnabled()) { - this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort()); + if (configuration.isQueryEnabled() && bindAddr instanceof InetSocketAddress) { + this.cm.queryBind(((InetSocketAddress) bindAddr).getHostString(), + configuration.getQueryPort()); } Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics()); @@ -362,8 +365,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { logger.info("Loaded {} plugins", pluginManager.getPlugins().size()); } - public Bootstrap createBootstrap(@Nullable EventLoopGroup group) { - return this.cm.createWorker(group); + public Bootstrap createBootstrap(@Nullable EventLoopGroup group, SocketAddress target) { + return this.cm.createWorker(group, target); } public ChannelInitializer getBackendChannelInitializer() { @@ -443,7 +446,9 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { } // If we have a new bind address, bind to it - if (!configuration.getBind().equals(newConfiguration.getBind())) { + SocketAddress oldBind = configuration.getBind(); + SocketAddress newBind = newConfiguration.getBind(); + if (!configuration.getBind().equals(newBind)) { this.cm.bind(newConfiguration.getBind()); this.cm.close(configuration.getBind()); } @@ -451,12 +456,13 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { boolean queryPortChanged = newConfiguration.getQueryPort() != configuration.getQueryPort(); boolean queryAlreadyEnabled = configuration.isQueryEnabled(); boolean queryEnabled = newConfiguration.isQueryEnabled(); - if ((!queryEnabled && queryAlreadyEnabled) || queryPortChanged) { + if (oldBind instanceof InetSocketAddress + && ((!queryEnabled && queryAlreadyEnabled) || queryPortChanged)) { this.cm.close(new InetSocketAddress( - configuration.getBind().getHostString(), configuration.getQueryPort())); + ((InetSocketAddress) oldBind).getHostString(), configuration.getQueryPort())); } - if (queryEnabled && queryPortChanged) { - this.cm.queryBind(newConfiguration.getBind().getHostString(), + if (queryEnabled && queryPortChanged && configuration.getBind() instanceof InetSocketAddress) { + this.cm.queryBind(((InetSocketAddress) newConfiguration.getBind()).getHostString(), newConfiguration.getQueryPort()); } @@ -718,7 +724,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { } @Override - public InetSocketAddress getBoundAddress() { + public SocketAddress getBoundAddress() { if (configuration == null) { throw new IllegalStateException( "No configuration"); // even though you'll never get the chance... heh, heh diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 9bc352ff6..6743e0798 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -32,7 +32,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -247,7 +247,7 @@ public class VelocityConfiguration implements ProxyConfig { } } - public InetSocketAddress getBind() { + public SocketAddress getBind() { return AddressUtil.parseAndResolveAddress(bind); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java index 1b8728d20..fa593f144 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java @@ -35,6 +35,9 @@ import com.velocitypowered.proxy.server.VelocityRegisteredServer; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.unix.DomainSocketAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Optional; import java.util.StringJoiner; import net.kyori.adventure.identity.Identity; @@ -94,8 +97,16 @@ public class BungeeCordMessageResponder { ByteBuf buf = Unpooled.buffer(); ByteBufDataOutput out = new ByteBufDataOutput(buf); out.writeUTF("IP"); - out.writeUTF(player.getRemoteAddress().getHostString()); - out.writeInt(player.getRemoteAddress().getPort()); + + SocketAddress address = player.getRemoteAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress serverInetAddr = (InetSocketAddress) address; + out.writeUTF(serverInetAddr.getHostString()); + out.writeInt(serverInetAddr.getPort()); + } else { + out.writeUTF("unix://" + ((DomainSocketAddress) address).path()); + out.writeInt(0); + } sendResponseOnConnection(buf); } @@ -234,8 +245,15 @@ public class BungeeCordMessageResponder { out.writeUTF("IPOther"); out.writeUTF(player.getUsername()); - out.writeUTF(player.getRemoteAddress().getHostString()); - out.writeInt(player.getRemoteAddress().getPort()); + SocketAddress address = player.getRemoteAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress serverInetAddr = (InetSocketAddress) address; + out.writeUTF(serverInetAddr.getHostString()); + out.writeInt(serverInetAddr.getPort()); + } else { + out.writeUTF("unix://" + ((DomainSocketAddress) address).path()); + out.writeInt(0); + } sendResponseOnConnection(buf); }); @@ -248,8 +266,15 @@ public class BungeeCordMessageResponder { out.writeUTF("ServerIP"); out.writeUTF(info.getServerInfo().getName()); - out.writeUTF(info.getServerInfo().getAddress().getHostString()); - out.writeShort(info.getServerInfo().getAddress().getPort()); + SocketAddress address = info.getServerInfo().getAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress serverInetAddr = (InetSocketAddress) address; + out.writeUTF(serverInetAddr.getHostString()); + out.writeShort(serverInetAddr.getPort()); + } else { + out.writeUTF("unix://" + ((DomainSocketAddress) address).path()); + out.writeShort(0); + } sendResponseOnConnection(buf); }); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index 22c1dd821..5e22a0086 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -45,6 +45,8 @@ import com.velocitypowered.proxy.server.VelocityRegisteredServer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; @@ -98,9 +100,10 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, CompletableFuture result = new CompletableFuture<>(); // Note: we use the event loop for the connection the player is on. This reduces context // switches. - server.createBootstrap(proxyPlayer.getConnection().eventLoop()) + SocketAddress destinationAddress = registeredServer.getServerInfo().getAddress(); + server.createBootstrap(proxyPlayer.getConnection().eventLoop(), destinationAddress) .handler(server.getBackendChannelInitializer()) - .connect(registeredServer.getServerInfo().getAddress()) + .connect(destinationAddress) .addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { connection = new MinecraftConnection(future.channel(), server); @@ -124,23 +127,37 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, } String getPlayerRemoteAddressAsString() { - final String addr = proxyPlayer.getRemoteAddress().getAddress().getHostAddress(); - int ipv6ScopeIdx = addr.indexOf('%'); - if (ipv6ScopeIdx == -1) { - return addr; - } else { - return addr.substring(0, ipv6ScopeIdx); + final SocketAddress address = proxyPlayer.getRemoteAddress(); + if (!(address instanceof InetSocketAddress)) { + return address.toString(); } + final String host = ((InetSocketAddress) address).getAddress().getHostAddress(); + int ipv6ScopeIdx = host.indexOf('%'); + if (ipv6ScopeIdx == -1) { + return host; + } + return host.substring(0, ipv6ScopeIdx); + } + + private String getHandshakeRemoteAddress() { + return proxyPlayer.getVirtualHost() + .map(InetSocketAddress::getHostString) + .or(() -> Optional.of(registeredServer.getServerInfo().getAddress()) + .filter(addr -> addr instanceof InetSocketAddress) + .map(addr -> ((InetSocketAddress) addr).getHostString())) + .orElse(""); } private String createLegacyForwardingAddress(UnaryOperator> propertiesTransform) { // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, // separated by \0 (the null byte). In order, you send the original host, the player's IP, their // UUID (undashed), and if you are in online-mode, their login properties (from Mojang). + SocketAddress playerRemoteAddress = proxyPlayer.getRemoteAddress(); + if (!(playerRemoteAddress instanceof InetSocketAddress)) { + return getHandshakeRemoteAddress(); + } StringBuilder data = new StringBuilder() - .append(proxyPlayer.getVirtualHost() - .orElseGet(() -> registeredServer.getServerInfo().getAddress()) - .getHostString()) + .append(getHandshakeRemoteAddress()) .append('\0') .append(getPlayerRemoteAddressAsString()) .append('\0') @@ -171,10 +188,6 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, // Initiate the handshake. ProtocolVersion protocolVersion = proxyPlayer.getConnection().getProtocolVersion(); - String playerVhost = proxyPlayer.getVirtualHost() - .orElseGet(() -> registeredServer.getServerInfo().getAddress()) - .getHostString(); - Handshake handshake = new Handshake(); handshake.setNextStatus(StateRegistry.LOGIN_ID); handshake.setProtocolVersion(protocolVersion); @@ -184,12 +197,15 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, byte[] secret = server.getConfiguration().getForwardingSecret(); handshake.setServerAddress(createBungeeGuardForwardingAddress(secret)); } else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) { - handshake.setServerAddress(playerVhost + HANDSHAKE_HOSTNAME_TOKEN); + handshake.setServerAddress(getHandshakeRemoteAddress() + HANDSHAKE_HOSTNAME_TOKEN); } else { - handshake.setServerAddress(playerVhost); + handshake.setServerAddress(getHandshakeRemoteAddress()); } - handshake.setPort(registeredServer.getServerInfo().getAddress().getPort()); + SocketAddress destinationAddr = registeredServer.getServerInfo().getAddress(); + if (destinationAddr instanceof InetSocketAddress) { + handshake.setPort(((InetSocketAddress) destinationAddr).getPort()); + } mc.delayedWrite(handshake); mc.setProtocolVersion(protocolVersion); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 3d9d69aef..55a15356a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -81,6 +81,7 @@ import com.velocitypowered.proxy.util.collect.CappedSet; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.ArrayDeque; import java.util.Collection; import java.util.Collections; @@ -304,8 +305,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } @Override - public InetSocketAddress getRemoteAddress() { - return (InetSocketAddress) connection.getRemoteAddress(); + public SocketAddress getRemoteAddress() { + return connection.getRemoteAddress(); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index c5668db2a..4126c9b12 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -38,6 +38,7 @@ import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.epoll.EpollChannelOption; import io.netty.util.concurrent.GlobalEventExecutor; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.HashMap; import java.util.Map; import org.apache.logging.log4j.LogManager; @@ -57,7 +58,7 @@ public final class ConnectionManager { private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20, 1 << 21); private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); - private final Map endpoints = new HashMap<>(); + private final Map endpoints = new HashMap<>(); private final TransportType transportType; private final EventLoopGroup bossGroup; private final EventLoopGroup workerGroup; @@ -113,18 +114,20 @@ public final class ConnectionManager { * * @param address the address to bind to */ - public void bind(final InetSocketAddress address) { + public void bind(final SocketAddress address) { final ServerBootstrap bootstrap = new ServerBootstrap() - .channelFactory(this.transportType.serverSocketChannelFactory) + .channelFactory(this.transportType.getServerChannelFactory(address)) .group(this.bossGroup, this.workerGroup) .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK) .childHandler(this.serverChannelInitializer.get()) - .childOption(ChannelOption.TCP_NODELAY, true) - .childOption(ChannelOption.IP_TOS, 0x18) .localAddress(address); - if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) { - bootstrap.option(EpollChannelOption.TCP_FASTOPEN, 3); + if (address instanceof InetSocketAddress) { + bootstrap.childOption(ChannelOption.TCP_NODELAY, true) + .childOption(ChannelOption.IP_TOS, 0x18); + if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) { + bootstrap.option(EpollChannelOption.TCP_FASTOPEN, 3); + } } bootstrap.bind() @@ -173,14 +176,15 @@ public final class ConnectionManager { } /** - * Creates a TCP {@link Bootstrap} using Velocity's event loops. + * Creates a {@link Bootstrap} using Velocity's event loops. * * @param group the event loop group to use. Use {@code null} for the default worker group. + * @param target the address the client will connect to * @return a new {@link Bootstrap} */ - public Bootstrap createWorker(@Nullable EventLoopGroup group) { + public Bootstrap createWorker(@Nullable EventLoopGroup group, SocketAddress target) { Bootstrap bootstrap = new Bootstrap() - .channelFactory(this.transportType.socketChannelFactory) + .channelFactory(this.transportType.getClientChannelFactory(target)) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.server.getConfiguration().getConnectTimeout()) @@ -197,7 +201,7 @@ public final class ConnectionManager { * * @param oldBind the endpoint to close */ - public void close(InetSocketAddress oldBind) { + public void close(SocketAddress oldBind) { Endpoint endpoint = endpoints.remove(oldBind); // Fire proxy close event to notify plugins of socket close. We block since plugins @@ -215,8 +219,8 @@ public final class ConnectionManager { * Closes all endpoints. */ public void shutdown() { - for (final Map.Entry entry : this.endpoints.entrySet()) { - final InetSocketAddress address = entry.getKey(); + for (final Map.Entry entry : this.endpoints.entrySet()) { + final SocketAddress address = entry.getKey(); final Endpoint endpoint = entry.getValue(); // Fire proxy close event to notify plugins of socket close. We block since plugins diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java b/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java index a7c65fef5..bef9f0377 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java @@ -18,11 +18,15 @@ package com.velocitypowered.proxy.network; import com.velocitypowered.proxy.util.concurrent.VelocityNettyThreadFactory; +import io.netty.channel.Channel; import io.netty.channel.ChannelFactory; import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollDatagramChannel; +import io.netty.channel.epoll.EpollDomainSocketChannel; import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollServerDomainSocketChannel; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.epoll.EpollSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; @@ -32,6 +36,10 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.DomainSocketChannel; +import io.netty.channel.unix.ServerDomainSocketChannel; +import java.net.SocketAddress; import java.util.concurrent.ThreadFactory; import java.util.function.BiFunction; @@ -42,27 +50,37 @@ public enum TransportType { NIO("NIO", NioServerSocketChannel::new, NioSocketChannel::new, NioDatagramChannel::new, + null, + null, (name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))), EPOLL("epoll", EpollServerSocketChannel::new, EpollSocketChannel::new, EpollDatagramChannel::new, + EpollServerDomainSocketChannel::new, + EpollDomainSocketChannel::new, (name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))); final String name; final ChannelFactory serverSocketChannelFactory; final ChannelFactory socketChannelFactory; final ChannelFactory datagramChannelFactory; + final ChannelFactory domainServerSocketChannelFactory; + final ChannelFactory domainSocketChannelFactory; final BiFunction eventLoopGroupFactory; TransportType(final String name, final ChannelFactory serverSocketChannelFactory, final ChannelFactory socketChannelFactory, final ChannelFactory datagramChannelFactory, + final ChannelFactory domainServerSocketChannelFactory, + final ChannelFactory domainSocketChannelFactory, final BiFunction eventLoopGroupFactory) { this.name = name; this.serverSocketChannelFactory = serverSocketChannelFactory; this.socketChannelFactory = socketChannelFactory; this.datagramChannelFactory = datagramChannelFactory; + this.domainServerSocketChannelFactory = domainServerSocketChannelFactory; + this.domainSocketChannelFactory = domainSocketChannelFactory; this.eventLoopGroupFactory = eventLoopGroupFactory; } @@ -71,6 +89,40 @@ public enum TransportType { return this.name; } + /** + * Returns the channel factory to use to listen on the specified socket address. + * + * @param address the address we want to listen on + * @return the channel factory + */ + public ChannelFactory getServerChannelFactory(SocketAddress address) { + if (address instanceof DomainSocketAddress) { + if (this.domainServerSocketChannelFactory == null) { + throw new IllegalArgumentException( + "Domain sockets are not available for non-Linux platforms"); + } + return this.domainServerSocketChannelFactory; + } + return this.serverSocketChannelFactory; + } + + /** + * Returns the channel factory to use to connect on the specified socket address. + * + * @param address the address we want to connect to + * @return the channel factory + */ + public ChannelFactory getClientChannelFactory(SocketAddress address) { + if (address instanceof DomainSocketAddress) { + if (this.domainSocketChannelFactory == null) { + throw new IllegalArgumentException( + "Domain sockets are not available for non-Linux platforms"); + } + return this.domainSocketChannelFactory; + } + return this.socketChannelFactory; + } + public EventLoopGroup createEventLoopGroup(final Type type) { return this.eventLoopGroupFactory.apply(this.name, type); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GameSpyQueryHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GameSpyQueryHandler.java index 57c0b318a..721c3b062 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GameSpyQueryHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GameSpyQueryHandler.java @@ -35,6 +35,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.DatagramPacket; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.ArrayList; @@ -92,8 +93,8 @@ public class GameSpyQueryHandler extends SimpleChannelInboundHandler pingFuture = new CompletableFuture<>(); - server.createBootstrap(loop) + server.createBootstrap(loop, serverInfo.getAddress()) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java index 42c5228e8..ef1afbd8c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java @@ -19,8 +19,11 @@ package com.velocitypowered.proxy.util; import com.google.common.base.Preconditions; import com.google.common.net.InetAddresses; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.unix.DomainSocketAddress; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.URI; /** @@ -35,14 +38,16 @@ public final class AddressUtil { } /** - * Attempts to parse an IP address of the form {@code 127.0.0.1:25565}. The returned - * {@link InetSocketAddress} is not resolved. + * Attempts to parse a socket address of the form {@code 127.0.0.1:25565}. The returned + * {@link SocketAddress} is not resolved if it is a {@link InetSocketAddress}. * * @param ip the IP to parse * @return the parsed address */ - public static InetSocketAddress parseAddress(String ip) { - Preconditions.checkNotNull(ip, "ip"); + public static SocketAddress parseAddress(String ip) { + if (ip.startsWith("unix://") && Epoll.isAvailable()) { + return new DomainSocketAddress(ip.substring("unix://".length())); + } URI uri = URI.create("tcp://" + ip); if (uri.getHost() == null) { throw new IllegalStateException("Invalid hostname/IP " + ip); @@ -58,13 +63,16 @@ public final class AddressUtil { } /** - * Attempts to parse an IP address of the form {@code 127.0.0.1:25565}. The returned - * {@link InetSocketAddress} is resolved. + * Attempts to parse a socket address of the form {@code 127.0.0.1:25565}. The returned + * {@link SocketAddress} is resolved if it is a {@link InetSocketAddress}. * * @param ip the IP to parse * @return the parsed address */ - public static InetSocketAddress parseAndResolveAddress(String ip) { + public static SocketAddress parseAndResolveAddress(String ip) { + if (ip.startsWith("unix://") && Epoll.isAvailable()) { + return new DomainSocketAddress(ip.substring("unix://".length())); + } Preconditions.checkNotNull(ip, "ip"); URI uri = URI.create("tcp://" + ip); if (uri.getHost() == null) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java index d8cdb7c3d..12585d063 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -38,6 +38,7 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.List; import java.util.Map; @@ -196,14 +197,19 @@ public enum InformationUtils { public static JsonObject collectServerInfo(RegisteredServer server) { JsonObject info = new JsonObject(); info.addProperty("currentPlayers", server.getPlayersConnected().size()); - InetSocketAddress iaddr = server.getServerInfo().getAddress(); - if (iaddr.isUnresolved()) { - // Greetings form Netty 4aa10db9 - info.addProperty("host", iaddr.getHostString()); + SocketAddress addr = server.getServerInfo().getAddress(); + if (addr instanceof InetSocketAddress) { + InetSocketAddress iaddr = (InetSocketAddress) addr; + if (iaddr.isUnresolved()) { + // Greetings form Netty 4aa10db9 + info.addProperty("host", iaddr.getHostString()); + } else { + info.addProperty("host", anonymizeInetAddress(iaddr.getAddress())); + } + info.addProperty("port", iaddr.getPort()); } else { - info.addProperty("host", anonymizeInetAddress(iaddr.getAddress())); + info.addProperty("bind", addr.toString()); } - info.addProperty("port", iaddr.getPort()); return info; }