Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

full support for UnixDomainSocket backend #1173

Open
wants to merge 8 commits into
base: dev/3.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -19,15 +23,15 @@
public final class ServerInfo implements Comparable<ServerInfo> {

private final String name;
private final InetSocketAddress address;
private final SocketAddress address;

/**
* Creates a new ServerInfo object.
*
* @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");
}
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Channel> getBackendChannelInitializer() {
return this.cm.backendChannelInitializer.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,22 @@
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;
import java.util.Map;
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;
Expand Down Expand Up @@ -99,34 +105,40 @@ public CompletableFuture<Impl> connect() {
CompletableFuture<Impl> 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;
}

Expand All @@ -140,13 +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<List<Property>> 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())
.append('\0')
StringBuilder data = new StringBuilder().append(getRemoteAddress())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this changes the intended behavior here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unix socket hoststring like /xxx/xxx is a filename, but inetsocketaddress.getHostString is a ipaddress, because of these two differences, resulting in different behaviors?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i missing a .append('\0')

.append(getPlayerRemoteAddressAsString())
.append('\0')
.append(proxyPlayer.getGameProfile().getUndashedId())
Expand Down Expand Up @@ -174,9 +199,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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no


Handshake handshake = new Handshake();
handshake.setNextStatus(StateRegistry.LOGIN_ID);
Expand All @@ -192,13 +218,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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

epoll is epoll, we can't just slap it on every single connection type, that is not how that works

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so i patch the paper

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so i patch the paper

import io.netty.channel.epoll.EpollServerDomainSocketChannel;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
Expand All @@ -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;

Expand All @@ -47,31 +52,43 @@ 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;
final ChannelFactory<? extends ServerSocketChannel> serverSocketChannelFactory;
final ChannelFactory<? extends SocketChannel> socketChannelFactory;
final ChannelFactory<? extends DatagramChannel> datagramChannelFactory;
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory;
final ChannelFactory<? extends ServerDomainSocketChannel> domainServerSocketChannelFactory;
final ChannelFactory<? extends DomainSocketChannel> domainSocketChannelFactory;

TransportType(final String name,
final ChannelFactory<? extends ServerSocketChannel> serverSocketChannelFactory,
final ChannelFactory<? extends SocketChannel> socketChannelFactory,
final ChannelFactory<? extends DatagramChannel> datagramChannelFactory,
final ChannelFactory<? extends ServerDomainSocketChannel> domainServerSocketChannelFactory,
final ChannelFactory<? extends DomainSocketChannel> domainSocketChannelFactory,
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory) {
this.name = name;
this.serverSocketChannelFactory = serverSocketChannelFactory;
this.socketChannelFactory = socketChannelFactory;
this.datagramChannelFactory = datagramChannelFactory;
this.domainServerSocketChannelFactory = domainServerSocketChannelFactory;
this.domainSocketChannelFactory = domainSocketChannelFactory;
this.eventLoopGroupFactory = eventLoopGroupFactory;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading