Skip to content

Commit 4148fa9

Browse files
AlexProgrammerDEKonicaionebeastchris
authored
Static sizer and timeout handlers in the pipeline (#833)
* Improve pipeline This simplifies all pipeline code and ensures some listeners like the sizer are always present. The code already assumed that the sizer is always there and thus causes issues. The sizer can be deactivated still now and has pretty much no performance losses from this. The profit from this PR is that there is less logic with modifying the PR and thus developers interacting with the channel can assume specific things about the order and placements of elements in the pipeline. This will be useful once ViaVersion is supported, and it is expected that certain elements always are in the pipeline and don't change. My plan is to also always have an encryption and compression handler in the pipeline that is controlled via AttributeKeys from netty, but for that first #828 needs to be merged. So this PR only completes the goal partially, but that's fine. PR is ready for review like it is right now. * Revert some stuff * Fix channel race condition * Fix closing race condition * Prevent client race conditions. * Fix test failure, idk how, idk why, but it works now * Address review * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/BuiltinFlags.java Co-authored-by: Konicai <[email protected]> * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/BuiltinFlags.java Co-authored-by: Konicai <[email protected]> * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/BuiltinFlags.java Co-authored-by: chris <[email protected]> * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpServer.java Co-authored-by: chris <[email protected]> * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/BuiltinFlags.java Co-authored-by: chris <[email protected]> * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/BuiltinFlags.java Co-authored-by: chris <[email protected]> * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpServer.java Co-authored-by: chris <[email protected]> * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpServer.java Co-authored-by: chris <[email protected]> * Update protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpServer.java Co-authored-by: chris <[email protected]> --------- Co-authored-by: Konicai <[email protected]> Co-authored-by: chris <[email protected]>
1 parent 716f229 commit 4148fa9

File tree

6 files changed

+172
-282
lines changed

6 files changed

+172
-282
lines changed

protocol/src/main/java/org/geysermc/mcprotocollib/network/BuiltinFlags.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
* Built-in PacketLib session flags.
77
*/
88
public class BuiltinFlags {
9-
public static final Flag<Boolean> ENABLE_CLIENT_PROXY_PROTOCOL = new Flag<>("enable-client-proxy-protocol", Boolean.class);
109

10+
/**
11+
* Enables HAProxy protocol support.
12+
* When this value is not null it represents the ip and port the client claims the connection is from.
13+
*/
1114
public static final Flag<InetSocketAddress> CLIENT_PROXIED_ADDRESS = new Flag<>("client-proxied-address", InetSocketAddress.class);
1215

1316
/**
@@ -20,6 +23,24 @@ public class BuiltinFlags {
2023
*/
2124
public static final Flag<Boolean> TCP_FAST_OPEN = new Flag<>("tcp-fast-open", Boolean.class);
2225

26+
/**
27+
* Connection timeout in seconds.
28+
* Only used by the client.
29+
*/
30+
public static final Flag<Integer> CLIENT_CONNECT_TIMEOUT = new Flag<>("client-connect-timeout", Integer.class);
31+
32+
/**
33+
* Read timeout in seconds.
34+
* Used by both the server and client.
35+
*/
36+
public static final Flag<Integer> READ_TIMEOUT = new Flag<>("read-timeout", Integer.class);
37+
38+
/**
39+
* Write timeout in seconds.
40+
* Used by both the server and client.
41+
*/
42+
public static final Flag<Integer> WRITE_TIMEOUT = new Flag<>("write-timeout", Integer.class);
43+
2344
private BuiltinFlags() {
2445
}
2546
}

protocol/src/main/java/org/geysermc/mcprotocollib/network/Session.java

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public interface Session {
3737
* @param wait Whether to wait for the connection to be established before returning.
3838
* @param transferring Whether the session is a client being transferred.
3939
*/
40-
public void connect(boolean wait, boolean transferring);
40+
void connect(boolean wait, boolean transferring);
4141

4242
/**
4343
* Gets the host the session is connected to.
@@ -138,7 +138,7 @@ public interface Session {
138138
*
139139
* @param flags Collection of flags
140140
*/
141-
public void setFlags(Map<String, Object> flags);
141+
void setFlags(Map<String, Object> flags);
142142

143143
/**
144144
* Gets the listeners listening on this session.
@@ -204,48 +204,6 @@ public interface Session {
204204
*/
205205
void enableEncryption(PacketEncryption encryption);
206206

207-
/**
208-
* Gets the connect timeout for this session in seconds.
209-
*
210-
* @return The session's connect timeout.
211-
*/
212-
int getConnectTimeout();
213-
214-
/**
215-
* Sets the connect timeout for this session in seconds.
216-
*
217-
* @param timeout Connect timeout to set.
218-
*/
219-
void setConnectTimeout(int timeout);
220-
221-
/**
222-
* Gets the read timeout for this session in seconds.
223-
*
224-
* @return The session's read timeout.
225-
*/
226-
int getReadTimeout();
227-
228-
/**
229-
* Sets the read timeout for this session in seconds.
230-
*
231-
* @param timeout Read timeout to set.
232-
*/
233-
void setReadTimeout(int timeout);
234-
235-
/**
236-
* Gets the write timeout for this session in seconds.
237-
*
238-
* @return The session's write timeout.
239-
*/
240-
int getWriteTimeout();
241-
242-
/**
243-
* Sets the write timeout for this session in seconds.
244-
*
245-
* @param timeout Write timeout to set.
246-
*/
247-
void setWriteTimeout(int timeout);
248-
249207
/**
250208
* Returns true if the session is connected.
251209
*

protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java

Lines changed: 83 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
import io.netty.buffer.ByteBuf;
55
import io.netty.channel.AddressedEnvelope;
66
import io.netty.channel.Channel;
7-
import io.netty.channel.ChannelFuture;
8-
import io.netty.channel.ChannelHandlerContext;
9-
import io.netty.channel.ChannelInboundHandlerAdapter;
107
import io.netty.channel.ChannelInitializer;
118
import io.netty.channel.ChannelOption;
129
import io.netty.channel.ChannelPipeline;
@@ -25,6 +22,8 @@
2522
import io.netty.handler.proxy.HttpProxyHandler;
2623
import io.netty.handler.proxy.Socks4ProxyHandler;
2724
import io.netty.handler.proxy.Socks5ProxyHandler;
25+
import io.netty.handler.timeout.ReadTimeoutHandler;
26+
import io.netty.handler.timeout.WriteTimeoutHandler;
2827
import io.netty.resolver.dns.DnsNameResolver;
2928
import io.netty.resolver.dns.DnsNameResolverBuilder;
3029
import io.netty.util.concurrent.DefaultThreadFactory;
@@ -40,6 +39,7 @@
4039
import java.net.InetAddress;
4140
import java.net.InetSocketAddress;
4241
import java.net.UnknownHostException;
42+
import java.util.concurrent.CompletableFuture;
4343
import java.util.concurrent.ThreadFactory;
4444
import java.util.concurrent.TimeUnit;
4545

@@ -90,56 +90,51 @@ public void connect(boolean wait, boolean transferring) {
9090
createTcpEventLoopGroup();
9191
}
9292

93-
try {
94-
final Bootstrap bootstrap = new Bootstrap()
95-
.channelFactory(TRANSPORT_TYPE.socketChannelFactory())
96-
.option(ChannelOption.TCP_NODELAY, true)
97-
.option(ChannelOption.IP_TOS, 0x18)
98-
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout() * 1000)
99-
.group(EVENT_LOOP_GROUP)
100-
.remoteAddress(resolveAddress())
101-
.localAddress(bindAddress, bindPort)
102-
.handler(new ChannelInitializer<>() {
103-
@Override
104-
public void initChannel(Channel channel) {
105-
PacketProtocol protocol = getPacketProtocol();
106-
protocol.newClientSession(TcpClientSession.this, transferring);
107-
108-
ChannelPipeline pipeline = channel.pipeline();
109-
110-
refreshReadTimeoutHandler(channel);
111-
refreshWriteTimeoutHandler(channel);
112-
113-
addProxy(pipeline);
114-
115-
int size = protocol.getPacketHeader().getLengthSize();
116-
if (size > 0) {
117-
pipeline.addLast("sizer", new TcpPacketSizer(TcpClientSession.this, size));
118-
}
119-
120-
pipeline.addLast("codec", new TcpPacketCodec(TcpClientSession.this, true));
121-
pipeline.addLast("manager", TcpClientSession.this);
122-
123-
addHAProxySupport(pipeline);
124-
}
125-
});
93+
final Bootstrap bootstrap = new Bootstrap()
94+
.channelFactory(TRANSPORT_TYPE.socketChannelFactory())
95+
.option(ChannelOption.TCP_NODELAY, true)
96+
.option(ChannelOption.IP_TOS, 0x18)
97+
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getFlag(BuiltinFlags.CLIENT_CONNECT_TIMEOUT, 30) * 1000)
98+
.group(EVENT_LOOP_GROUP)
99+
.remoteAddress(resolveAddress())
100+
.localAddress(bindAddress, bindPort)
101+
.handler(new ChannelInitializer<>() {
102+
@Override
103+
public void initChannel(Channel channel) {
104+
PacketProtocol protocol = getPacketProtocol();
105+
protocol.newClientSession(TcpClientSession.this, transferring);
126106

127-
if (getFlag(BuiltinFlags.TCP_FAST_OPEN, false) && TRANSPORT_TYPE.supportsTcpFastOpenClient()) {
128-
bootstrap.option(ChannelOption.TCP_FASTOPEN_CONNECT, true);
129-
}
107+
ChannelPipeline pipeline = channel.pipeline();
130108

131-
ChannelFuture future = bootstrap.connect();
132-
if (wait) {
133-
future.sync();
134-
}
109+
addProxy(pipeline);
135110

136-
future.addListener((futureListener) -> {
137-
if (!futureListener.isSuccess()) {
138-
exceptionCaught(null, futureListener.cause());
111+
initializeHAProxySupport(channel);
112+
113+
pipeline.addLast("read-timeout", new ReadTimeoutHandler(getFlag(BuiltinFlags.READ_TIMEOUT, 30)));
114+
pipeline.addLast("write-timeout", new WriteTimeoutHandler(getFlag(BuiltinFlags.WRITE_TIMEOUT, 0)));
115+
116+
pipeline.addLast("sizer", new TcpPacketSizer(protocol.getPacketHeader(), getCodecHelper()));
117+
118+
pipeline.addLast("codec", new TcpPacketCodec(TcpClientSession.this, true));
119+
pipeline.addLast("manager", TcpClientSession.this);
139120
}
140121
});
141-
} catch (Throwable t) {
142-
exceptionCaught(null, t);
122+
123+
if (getFlag(BuiltinFlags.TCP_FAST_OPEN, false) && TRANSPORT_TYPE.supportsTcpFastOpenClient()) {
124+
bootstrap.option(ChannelOption.TCP_FASTOPEN_CONNECT, true);
125+
}
126+
127+
CompletableFuture<Void> handleFuture = new CompletableFuture<>();
128+
bootstrap.connect().addListener((futureListener) -> {
129+
if (!futureListener.isSuccess()) {
130+
exceptionCaught(null, futureListener.cause());
131+
}
132+
133+
handleFuture.complete(null);
134+
});
135+
136+
if (wait) {
137+
handleFuture.join();
143138
}
144139
}
145140

@@ -155,8 +150,8 @@ private InetSocketAddress resolveAddress() {
155150
if (getFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, true) && (!this.host.matches(IP_REGEX) && !this.host.equalsIgnoreCase("localhost"))) {
156151
AddressedEnvelope<DnsResponse, InetSocketAddress> envelope = null;
157152
try (DnsNameResolver resolver = new DnsNameResolverBuilder(EVENT_LOOP_GROUP.next())
158-
.channelFactory(TRANSPORT_TYPE.datagramChannelFactory())
159-
.build()) {
153+
.channelFactory(TRANSPORT_TYPE.datagramChannelFactory())
154+
.build()) {
160155
envelope = resolver.query(new DefaultDnsQuestion(name, DnsRecordType.SRV)).get();
161156

162157
DnsResponse response = envelope.content();
@@ -206,54 +201,52 @@ private InetSocketAddress resolveAddress() {
206201
}
207202

208203
private void addProxy(ChannelPipeline pipeline) {
209-
if (proxy != null) {
210-
switch (proxy.type()) {
211-
case HTTP -> {
212-
if (proxy.username() != null && proxy.password() != null) {
213-
pipeline.addFirst("proxy", new HttpProxyHandler(proxy.address(), proxy.username(), proxy.password()));
214-
} else {
215-
pipeline.addFirst("proxy", new HttpProxyHandler(proxy.address()));
216-
}
204+
if (proxy == null) {
205+
return;
206+
}
207+
208+
switch (proxy.type()) {
209+
case HTTP -> {
210+
if (proxy.username() != null && proxy.password() != null) {
211+
pipeline.addLast("proxy", new HttpProxyHandler(proxy.address(), proxy.username(), proxy.password()));
212+
} else {
213+
pipeline.addLast("proxy", new HttpProxyHandler(proxy.address()));
217214
}
218-
case SOCKS4 -> {
219-
if (proxy.username() != null) {
220-
pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.address(), proxy.username()));
221-
} else {
222-
pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.address()));
223-
}
215+
}
216+
case SOCKS4 -> {
217+
if (proxy.username() != null) {
218+
pipeline.addLast("proxy", new Socks4ProxyHandler(proxy.address(), proxy.username()));
219+
} else {
220+
pipeline.addLast("proxy", new Socks4ProxyHandler(proxy.address()));
224221
}
225-
case SOCKS5 -> {
226-
if (proxy.username() != null && proxy.password() != null) {
227-
pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.address(), proxy.username(), proxy.password()));
228-
} else {
229-
pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.address()));
230-
}
222+
}
223+
case SOCKS5 -> {
224+
if (proxy.username() != null && proxy.password() != null) {
225+
pipeline.addLast("proxy", new Socks5ProxyHandler(proxy.address(), proxy.username(), proxy.password()));
226+
} else {
227+
pipeline.addLast("proxy", new Socks5ProxyHandler(proxy.address()));
231228
}
232-
default -> throw new UnsupportedOperationException("Unsupported proxy type: " + proxy.type());
233229
}
230+
default -> throw new UnsupportedOperationException("Unsupported proxy type: " + proxy.type());
234231
}
235232
}
236233

237-
private void addHAProxySupport(ChannelPipeline pipeline) {
234+
private void initializeHAProxySupport(Channel channel) {
238235
InetSocketAddress clientAddress = getFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS);
239-
if (getFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, false) && clientAddress != null) {
240-
pipeline.addFirst("proxy-protocol-packet-sender", new ChannelInboundHandlerAdapter() {
241-
@Override
242-
public void channelActive(ChannelHandlerContext ctx) throws Exception {
243-
HAProxyProxiedProtocol proxiedProtocol = clientAddress.getAddress() instanceof Inet4Address ? HAProxyProxiedProtocol.TCP4 : HAProxyProxiedProtocol.TCP6;
244-
InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress();
245-
ctx.channel().writeAndFlush(new HAProxyMessage(
246-
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol,
247-
clientAddress.getAddress().getHostAddress(), remoteAddress.getAddress().getHostAddress(),
248-
clientAddress.getPort(), remoteAddress.getPort()
249-
));
250-
ctx.pipeline().remove(this);
251-
ctx.pipeline().remove("proxy-protocol-encoder");
252-
super.channelActive(ctx);
253-
}
254-
});
255-
pipeline.addFirst("proxy-protocol-encoder", HAProxyMessageEncoder.INSTANCE);
236+
if (clientAddress == null) {
237+
return;
256238
}
239+
240+
channel.pipeline().addLast("proxy-protocol-encoder", HAProxyMessageEncoder.INSTANCE);
241+
HAProxyProxiedProtocol proxiedProtocol = clientAddress.getAddress() instanceof Inet4Address ? HAProxyProxiedProtocol.TCP4 : HAProxyProxiedProtocol.TCP6;
242+
InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
243+
channel.writeAndFlush(new HAProxyMessage(
244+
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol,
245+
clientAddress.getAddress().getHostAddress(), remoteAddress.getAddress().getHostAddress(),
246+
clientAddress.getPort(), remoteAddress.getPort()
247+
)).addListener(future -> {
248+
channel.pipeline().remove("proxy-protocol-encoder");
249+
});
257250
}
258251

259252
private static void createTcpEventLoopGroup() {
@@ -264,7 +257,7 @@ private static void createTcpEventLoopGroup() {
264257
EVENT_LOOP_GROUP = TRANSPORT_TYPE.eventLoopGroupFactory().apply(newThreadFactory());
265258

266259
Runtime.getRuntime().addShutdownHook(new Thread(
267-
() -> EVENT_LOOP_GROUP.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS)));
260+
() -> EVENT_LOOP_GROUP.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS)));
268261
}
269262

270263
protected static ThreadFactory newThreadFactory() {

0 commit comments

Comments
 (0)