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

Merge master into 1.21.4 #871

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,149 @@
package org.geysermc.mcprotocollib.network.helper;

import io.netty.buffer.ByteBuf;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DefaultDnsQuestion;
import io.netty.handler.codec.dns.DefaultDnsRawRecord;
import io.netty.handler.codec.dns.DefaultDnsRecordDecoder;
import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsSection;
import io.netty.handler.codec.haproxy.HAProxyCommand;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.codec.haproxy.HAProxyMessageEncoder;
import io.netty.handler.codec.haproxy.HAProxyProtocolVersion;
import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol;
import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.proxy.Socks4ProxyHandler;
import io.netty.handler.proxy.Socks5ProxyHandler;
import io.netty.resolver.dns.DnsNameResolver;
import io.netty.resolver.dns.DnsNameResolverBuilder;
import org.geysermc.mcprotocollib.network.BuiltinFlags;
import org.geysermc.mcprotocollib.network.ProxyInfo;
import org.geysermc.mcprotocollib.network.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;

public class NettyHelper {
private static final Logger log = LoggerFactory.getLogger(NettyHelper.class);
private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";

public static InetSocketAddress resolveAddress(Session session, EventLoop eventLoop, String host, int port) {
String name = session.getPacketProtocol().getSRVRecordPrefix() + "._tcp." + host;
log.debug("Attempting SRV lookup for \"{}\".", name);

if (session.getFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, true) && (!host.matches(IP_REGEX) && !host.equalsIgnoreCase("localhost"))) {
try (DnsNameResolver resolver = new DnsNameResolverBuilder(eventLoop)
.channelFactory(TransportHelper.TRANSPORT_TYPE.datagramChannelFactory())
.build()) {
AddressedEnvelope<DnsResponse, InetSocketAddress> envelope = resolver.query(new DefaultDnsQuestion(name, DnsRecordType.SRV)).get();
try {
DnsResponse response = envelope.content();
if (response.count(DnsSection.ANSWER) > 0) {
DefaultDnsRawRecord record = response.recordAt(DnsSection.ANSWER, 0);
if (record.type() == DnsRecordType.SRV) {
ByteBuf buf = record.content();
buf.skipBytes(4); // Skip priority and weight.

int tempPort = buf.readUnsignedShort();
String tempHost = DefaultDnsRecordDecoder.decodeName(buf);
if (tempHost.endsWith(".")) {
tempHost = tempHost.substring(0, tempHost.length() - 1);
}

log.debug("Found SRV record containing \"{}:{}\".", tempHost, tempPort);

host = tempHost;
port = tempPort;
} else {
log.debug("Received non-SRV record in response.");
}
} else {
log.debug("No SRV record found.");
}
} finally {
envelope.release();
}
} catch (Exception e) {
log.debug("Failed to resolve SRV record.", e);
}
} else {
log.debug("Not resolving SRV record for {}", host);
}

// Resolve host here
try {
InetAddress resolved = InetAddress.getByName(host);
log.debug("Resolved {} -> {}", host, resolved.getHostAddress());
return new InetSocketAddress(resolved, port);
} catch (UnknownHostException e) {
log.debug("Failed to resolve host, letting Netty do it instead.", e);
return InetSocketAddress.createUnresolved(host, port);
}
}

public static void initializeHAProxySupport(Session session, Channel channel) {
InetSocketAddress clientAddress = session.getFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS);
if (clientAddress == null) {
return;
}

channel.pipeline().addLast("proxy-protocol-encoder", HAProxyMessageEncoder.INSTANCE);
channel.pipeline().addLast("proxy-protocol-packet-sender", new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress();
HAProxyProxiedProtocol proxiedProtocol = clientAddress.getAddress() instanceof Inet4Address ? HAProxyProxiedProtocol.TCP4 : HAProxyProxiedProtocol.TCP6;
ctx.channel().writeAndFlush(new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol,
clientAddress.getAddress().getHostAddress(), remoteAddress.getAddress().getHostAddress(),
clientAddress.getPort(), remoteAddress.getPort()
)).addListener(future -> channel.pipeline().remove("proxy-protocol-encoder"));
ctx.pipeline().remove(this);

super.channelActive(ctx);
}
});
}

public static void addProxy(ProxyInfo proxy, ChannelPipeline pipeline) {
if (proxy == null) {
return;
}

switch (proxy.type()) {
case HTTP -> {
if (proxy.username() != null && proxy.password() != null) {
pipeline.addLast("proxy", new HttpProxyHandler(proxy.address(), proxy.username(), proxy.password()));
} else {
pipeline.addLast("proxy", new HttpProxyHandler(proxy.address()));
}
}
case SOCKS4 -> {
if (proxy.username() != null) {
pipeline.addLast("proxy", new Socks4ProxyHandler(proxy.address(), proxy.username()));
} else {
pipeline.addLast("proxy", new Socks4ProxyHandler(proxy.address()));
}
}
case SOCKS5 -> {
if (proxy.username() != null && proxy.password() != null) {
pipeline.addLast("proxy", new Socks5ProxyHandler(proxy.address(), proxy.username(), proxy.password()));
} else {
pipeline.addLast("proxy", new Socks5ProxyHandler(proxy.address()));
}
}
default -> throw new UnsupportedOperationException("Unsupported proxy type: " + proxy.type());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.util.function.Function;

public class TransportHelper {
public static final TransportHelper.TransportType TRANSPORT_TYPE = TransportHelper.determineTransportMethod();

public enum TransportMethod {
NIO, EPOLL, KQUEUE, IO_URING
}
Expand All @@ -45,7 +47,7 @@ public record TransportType(TransportMethod method,
boolean supportsTcpFastOpenClient) {
}

public static TransportType determineTransportMethod() {
private static TransportType determineTransportMethod() {
if (isClassAvailable("io.netty.incubator.channel.uring.IOUring") && IOUring.isAvailable()) {
return new TransportType(
TransportMethod.IO_URING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
public interface Packet {

/**
* Gets whether the packet has handling priority.
* If the result is true, the packet will be handled immediately after being
* decoded.
* Gets whether the packet should run on an async game thread rather than blocking the network (Netty) thread.
* Packets that qualify for this are usually packets with an ensureRunningOnSameThread call at the top of their packet listener method in the Minecraft code.
* Packets which need extra attention because they aren't "fully" handled async are marked using
* // GAME THREAD DETAIL comments in the MCProtocolLib code.
*
* @return Whether the packet has priority.
* @return Whether the packet be handled async from the Netty thread.
*/
default boolean isPriority() {
default boolean shouldRunOnGameThread() {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.geysermc.mcprotocollib.network.tcp;

import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.DefaultThreadFactory;

import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

public class DefaultPacketHandlerExecutor {
/**
* Controls whether non-priority packets are handled in a separate event loop
*/
public static boolean USE_EVENT_LOOP_FOR_PACKETS = true;
private static EventLoopGroup PACKET_EVENT_LOOP;
private static final int SHUTDOWN_QUIET_PERIOD_MS = 100;
private static final int SHUTDOWN_TIMEOUT_MS = 500;

public static Executor createExecutor() {
if (!USE_EVENT_LOOP_FOR_PACKETS) {
return Runnable::run;
}

if (PACKET_EVENT_LOOP == null) {
// See TcpClientSession.newThreadFactory() for details on
// daemon threads and their interaction with the runtime.
PACKET_EVENT_LOOP = new DefaultEventLoopGroup(new DefaultThreadFactory(DefaultPacketHandlerExecutor.class, true));
Runtime.getRuntime().addShutdownHook(new Thread(
() -> PACKET_EVENT_LOOP.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS)));
}

return PACKET_EVENT_LOOP.next();
}
}
Loading
Loading