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 fd686297ee..3d990316d7 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,11 @@ package com.velocitypowered.api.proxy.server; import com.google.common.base.Preconditions; + +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; @@ -19,7 +23,7 @@ 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 +31,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 +40,24 @@ public final String getName() { return name; } + /** + * use getSocketAddress() to get address + * @return address the address of the server to connect to + */ + @Deprecated public final InetSocketAddress getAddress() { + if (address instanceof InetSocketAddress) { + return (InetSocketAddress) address; + } else { + throw new UnsupportedOperationException("BackendServer is use Unix domain socket"); + } + } + + /** + * use getSocketAddress() to get address + * @return address the address of the server to connect to + */ + public final SocketAddress getSocketAddress() { 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 f113164b8b..af617139f0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -382,6 +382,10 @@ public Bootstrap createBootstrap(@Nullable EventLoopGroup group) { return this.cm.createWorker(group); } + public Bootstrap createDomainBootstrap(@Nullable EventLoopGroup group) { + return this.cm.createDomainWorker(group); + } + public ChannelInitializer getBackendChannelInitializer() { return this.cm.backendChannelInitializer.get(); } 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 1b8728d209..c7df133fc1 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,8 +35,13 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Optional; import java.util.StringJoiner; + +import io.netty.channel.unix.DomainSocketAddress; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.ComponentSerializer; @@ -248,8 +253,16 @@ private void processServerIp(ByteBufDataInput in) { out.writeUTF("ServerIP"); out.writeUTF(info.getServerInfo().getName()); - out.writeUTF(info.getServerInfo().getAddress().getHostString()); - out.writeShort(info.getServerInfo().getAddress().getPort()); + SocketAddress address = info.getServerInfo().getSocketAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress address1 = (InetSocketAddress) address; + out.writeUTF(address1.getHostString()); + out.writeShort(address1.getPort()); + } else { + DomainSocketAddress address1 = (DomainSocketAddress) address; + out.writeUTF(address1.toString()); + out.writeShort(-1); + } 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 9a4b7d72dc..f2ee686530 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 @@ -42,9 +42,13 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.server.VelocityRegisteredServer; +import io.netty.bootstrap.Bootstrap; 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; @@ -52,6 +56,8 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.UnaryOperator; + +import io.netty.channel.unix.DomainSocketAddress; import net.kyori.adventure.nbt.CompoundBinaryTag; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -99,34 +105,40 @@ public CompletableFuture connect() { 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()) - .handler(server.getBackendChannelInitializer()) - .connect(registeredServer.getServerInfo().getAddress()) - .addListener((ChannelFutureListener) future -> { - if (future.isSuccess()) { - connection = new MinecraftConnection(future.channel(), server); - connection.setAssociation(VelocityServerConnection.this); - future.channel().pipeline().addLast(HANDLER, connection); - - // Kick off the connection process - if (!connection.setActiveSessionHandler(StateRegistry.HANDSHAKE)) { - MinecraftSessionHandler handler = - new LoginSessionHandler(server, VelocityServerConnection.this, result); - connection.setActiveSessionHandler(StateRegistry.HANDSHAKE, handler); - connection.addSessionHandler(StateRegistry.LOGIN, handler); - } - - // Set the connection phase, which may, for future forge (or whatever), be - // determined - // at this point already - connectionPhase = connection.getType().getInitialBackendPhase(); - startHandshake(); - } else { - // Complete the result immediately. ConnectedPlayer will reset the in-flight - // connection. - result.completeExceptionally(future.cause()); - } - }); + SocketAddress address = registeredServer.getServerInfo().getSocketAddress(); + Bootstrap bootstrap; + if (address instanceof DomainSocketAddress) { + bootstrap = server.createDomainBootstrap(proxyPlayer.getConnection().eventLoop()); + } else { + bootstrap = server.createBootstrap(proxyPlayer.getConnection().eventLoop()); + } + bootstrap.handler(server.getBackendChannelInitializer()) + .connect(address) + .addListener((ChannelFutureListener) future -> { + if (future.isSuccess()) { + connection = new MinecraftConnection(future.channel(), server); + connection.setAssociation(VelocityServerConnection.this); + future.channel().pipeline().addLast(HANDLER, connection); + + // Kick off the connection process + if (!connection.setActiveSessionHandler(StateRegistry.HANDSHAKE)) { + MinecraftSessionHandler handler = + new LoginSessionHandler(server, VelocityServerConnection.this, result); + connection.setActiveSessionHandler(StateRegistry.HANDSHAKE, handler); + connection.addSessionHandler(StateRegistry.LOGIN, handler); + } + + // Set the connection phase, which may, for future forge (or whatever), be + // determined + // at this point already + connectionPhase = connection.getType().getInitialBackendPhase(); + startHandshake(); + } else { + // Complete the result immediately. ConnectedPlayer will reset the in-flight + // connection. + result.completeExceptionally(future.cause()); + } + }); return result; } @@ -140,12 +152,26 @@ String getPlayerRemoteAddressAsString() { } } + private String getRemoteAddress() { + var op = proxyPlayer.getVirtualHost(); + if (op.isPresent()) { + return op.get().getHostString(); + } + SocketAddress address = registeredServer.getServerInfo().getSocketAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress address1 = (InetSocketAddress) address; + return address1.getHostString(); + } else { + DomainSocketAddress address1 = (DomainSocketAddress) address; + return address1.toString(); + } + } + 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). - StringBuilder data = new StringBuilder().append(proxyPlayer.getVirtualHost().orElseGet(() -> - registeredServer.getServerInfo().getAddress()).getHostString()) + StringBuilder data = new StringBuilder().append(getRemoteAddress()) .append('\0') .append(getPlayerRemoteAddressAsString()) .append('\0') @@ -174,9 +200,10 @@ private void startHandshake() { // Initiate the handshake. ProtocolVersion protocolVersion = proxyPlayer.getConnection().getProtocolVersion(); - String playerVhost = - proxyPlayer.getVirtualHost().orElseGet(() -> registeredServer.getServerInfo().getAddress()) - .getHostString(); +// String playerVhost = +// proxyPlayer.getVirtualHost().orElseGet(() -> registeredServer.getServerInfo().getAddress()) +// .getHostString(); + String playerVhost = getRemoteAddress(); Handshake handshake = new Handshake(); handshake.setNextStatus(StateRegistry.LOGIN_ID); @@ -192,13 +219,19 @@ private void startHandshake() { handshake.setServerAddress(playerVhost); } - handshake.setPort(registeredServer.getServerInfo().getAddress().getPort()); + SocketAddress address = registeredServer.getServerInfo().getSocketAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress address1 = (InetSocketAddress) address; + handshake.setPort(address1.getPort()); + } else { + handshake.setPort(-1); + } mc.delayedWrite(handshake); mc.setProtocolVersion(protocolVersion); mc.setActiveSessionHandler(StateRegistry.LOGIN); if (proxyPlayer.getIdentifiedKey() == null - && proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { + && proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getUniqueId())); } else { mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getIdentifiedKey())); 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 6175f82e24..0f48eb7f43 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -191,6 +191,21 @@ public Bootstrap createWorker(@Nullable EventLoopGroup group) { return bootstrap; } + /** + * Creates a Unix domain {@link Bootstrap} using Velocity's event loops. + * + * @param group the event loop group to use. Use {@code null} for the default worker group. + * @return a new {@link Bootstrap} + */ + public Bootstrap createDomainWorker(@Nullable EventLoopGroup group) { + return new Bootstrap() + .channelFactory(this.transportType.domainSocketChannelFactory) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, + this.server.getConfiguration().getConnectTimeout()) + .group(group == null ? this.workerGroup : group) + .resolver(this.resolver.asGroup()); + } + /** * Closes the specified {@code oldBind} endpoint. * 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 6cbdfd8c54..50e399b94d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java @@ -25,6 +25,8 @@ import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.epoll.EpollDomainSocketChannel; +import io.netty.channel.epoll.EpollServerDomainSocketChannel; import io.netty.channel.kqueue.KQueue; import io.netty.channel.kqueue.KQueueDatagramChannel; import io.netty.channel.kqueue.KQueueEventLoopGroup; @@ -37,6 +39,9 @@ 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.DomainSocketChannel; +import io.netty.channel.unix.ServerDomainSocketChannel; + import java.util.concurrent.ThreadFactory; import java.util.function.BiFunction; @@ -47,14 +52,20 @@ public enum TransportType { NIO("NIO", NioServerSocketChannel::new, NioSocketChannel::new, NioDatagramChannel::new, + EpollServerDomainSocketChannel::new, + EpollDomainSocketChannel::new, (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))), KQUEUE("kqueue", KQueueServerSocketChannel::new, KQueueSocketChannel::new, KQueueDatagramChannel::new, + EpollServerDomainSocketChannel::new, + EpollDomainSocketChannel::new, (name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type))); final String name; @@ -62,16 +73,22 @@ public enum TransportType { final ChannelFactory socketChannelFactory; final ChannelFactory datagramChannelFactory; final BiFunction eventLoopGroupFactory; + final ChannelFactory domainServerSocketChannelFactory; + final ChannelFactory domainSocketChannelFactory; 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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java index ae93120aaf..e535109ffb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java @@ -28,7 +28,11 @@ import com.velocitypowered.proxy.protocol.packet.StatusRequest; import com.velocitypowered.proxy.protocol.packet.StatusResponse; import io.netty.channel.EventLoop; +import io.netty.channel.unix.DomainSocketAddress; + import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.concurrent.CompletableFuture; /** @@ -55,8 +59,16 @@ public class PingSessionHandler implements MinecraftSessionHandler { public void activated() { Handshake handshake = new Handshake(); handshake.setNextStatus(StateRegistry.STATUS_ID); - handshake.setServerAddress(server.getServerInfo().getAddress().getHostString()); - handshake.setPort(server.getServerInfo().getAddress().getPort()); + SocketAddress address = server.getServerInfo().getSocketAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress address1 = (InetSocketAddress) address; + handshake.setServerAddress(address1.getHostString()); + handshake.setPort(address1.getPort()); + } else { + DomainSocketAddress address1 = (DomainSocketAddress) address; + handshake.setServerAddress(address1.toString()); + handshake.setPort(-1); + } handshake.setProtocolVersion(version); connection.delayedWrite(handshake); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java index 837c46a252..c2dff72221 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -121,7 +121,7 @@ protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(HANDLER, new MinecraftConnection(ch, server)); } - }).connect(serverInfo.getAddress()).addListener((ChannelFutureListener) future -> { + }).connect(serverInfo.getSocketAddress()).addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); PingSessionHandler handler = new PingSessionHandler(pingFuture, 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 42c5228e89..cdf39a73f3 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,12 @@ 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; /** @@ -36,12 +40,21 @@ private AddressUtil() { /** * Attempts to parse an IP address of the form {@code 127.0.0.1:25565}. The returned - * {@link InetSocketAddress} is not resolved. + * {@link SocketAddress} is not resolved. * * @param ip the IP to parse * @return the parsed address */ - public static InetSocketAddress parseAddress(String ip) { + public static SocketAddress parseAddress(String ip) { + if (ip.startsWith("unix:")) { + if(Epoll.isAvailable()) { + return new DomainSocketAddress(ip.substring("unix:".length())); + } + else { + throw new UnsupportedOperationException("unix DomainSocket is unsupported"); + } + } + 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 d8cdb7c3d1..f4d2cfa0b3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -34,10 +34,9 @@ import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.network.TransportType; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; +import io.netty.channel.unix.DomainSocketAddress; + +import java.net.*; import java.util.List; import java.util.Map; @@ -196,14 +195,22 @@ public static String anonymizeInetAddress(InetAddress address) { 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 iaddr = server.getServerInfo().getSocketAddress(); + if (iaddr instanceof InetSocketAddress) { + InetSocketAddress address = (InetSocketAddress) iaddr; + if (address.isUnresolved()) { + // Greetings form Netty 4aa10db9 + info.addProperty("host", address.getHostString()); + } else { + info.addProperty("host", anonymizeInetAddress(address.getAddress())); + } + info.addProperty("port", address.getPort()); } else { - info.addProperty("host", anonymizeInetAddress(iaddr.getAddress())); + DomainSocketAddress address = (DomainSocketAddress) iaddr; + info.addProperty("host", address.toString()); + info.addProperty("port", 0); } - info.addProperty("port", iaddr.getPort()); return info; }