Implement proper NIO proxy support and remove synchronous support from

send/disconnect.
This commit is contained in:
Steveice10 2020-02-26 18:11:23 -08:00
parent d05bdadbbf
commit d725ce0d79
8 changed files with 178 additions and 96 deletions

View file

@ -49,7 +49,7 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
<version>4.1.45.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>

View file

@ -0,0 +1,106 @@
package com.github.steveice10.packetlib;
import java.net.SocketAddress;
/**
* Information describing a network proxy.
*/
public class ProxyInfo {
private Type type;
private SocketAddress address;
private boolean authenticated;
private String username;
private String password;
/**
* Creates a new unauthenticated ProxyInfo instance.
*
* @param type Type of proxy.
* @param address Network address of the proxy.
*/
public ProxyInfo(Type type, SocketAddress address) {
this.type = type;
this.address = address;
this.authenticated = false;
}
/**
* Creates a new authenticated ProxyInfo instance.
*
* @param type Type of proxy.
* @param address Network address of the proxy.
* @param username Username to authenticate with.
* @param password Password to authenticate with.
*/
public ProxyInfo(Type type, SocketAddress address, String username, String password) {
this(type, address);
this.authenticated = true;
this.username = username;
this.password = password;
}
/**
* Gets the proxy's type.
*
* @return The proxy's type.
*/
public Type getType() {
return this.type;
}
/**
* Gets the proxy's network address.
*
* @return The proxy's network address.
*/
public SocketAddress getAddress() {
return this.address;
}
/**
* Gets whether the proxy is authenticated with.
*
* @return Whether to authenticate with the proxy.
*/
public boolean isAuthenticated() {
return this.authenticated;
}
/**
* Gets the proxy's authentication username.
*
* @return The username to authenticate with.
*/
public String getUsername() {
return this.username;
}
/**
* Gets the proxy's authentication password.
*
* @return The password to authenticate with.
*/
public String getPassword() {
return this.password;
}
/**
* Supported proxy types.
*/
public enum Type {
/**
* HTTP proxy.
*/
HTTP,
/**
* SOCKS4 proxy.
*/
SOCKS4,
/**
* SOCKS5 proxy.
*/
SOCKS5;
}
}

View file

@ -89,6 +89,19 @@ public interface Session {
*/
public <T> T getFlag(String key);
/**
* Gets the value of the given flag as an instance of the given type. If this
* session belongs to a server, the server's flags will be checked for the flag
* as well. If no flag is found, the specified default value will be returned.
*
* @param <T> Type of the flag.
* @param key Key of the flag.
* @param def Default value of the flag.
* @return Value of the flag.
* @throws IllegalStateException If the flag's value isn't of the required type.
*/
public <T> T getFlag(String key, T def);
/**
* Sets the value of a flag. This does not change a server's flags if this session
* belongs to a server.
@ -203,14 +216,6 @@ public interface Session {
*/
public void disconnect(String reason);
/**
* Disconnects the session.
*
* @param reason Reason for disconnecting.
* @param wait Whether to wait for the session to be disconnected.
*/
public void disconnect(String reason, boolean wait);
/**
* Disconnects the session.
*
@ -218,13 +223,4 @@ public interface Session {
* @param cause Throwable responsible for disconnecting.
*/
public void disconnect(String reason, Throwable cause);
/**
* Disconnects the session.
*
* @param reason Reason for disconnecting.
* @param cause Throwable responsible for disconnecting.
* @param wait Whether to wait for the session to be disconnected.
*/
public void disconnect(String reason, Throwable cause, boolean wait);
}

View file

@ -26,9 +26,9 @@ public interface Packet {
public void write(NetOutput out) throws IOException;
/**
* Gets whether the packet has handling and writing priority.
* If the result is true, the thread will wait for the packet to finish writing
* when writing and the packet will be handled immediately after reading it.
* Gets whether the packet has handling priority.
* If the result is true, the packet will be handled immediately after being
* decoded.
*
* @return Whether the packet has priority.
*/

View file

@ -1,20 +0,0 @@
package com.github.steveice10.packetlib.tcp;
import io.netty.channel.ChannelFactory;
import io.netty.channel.socket.oio.OioSocketChannel;
import java.net.Proxy;
import java.net.Socket;
public class ProxyOioChannelFactory implements ChannelFactory<OioSocketChannel> {
private Proxy proxy;
public ProxyOioChannelFactory(Proxy proxy) {
this.proxy = proxy;
}
@Override
public OioSocketChannel newChannel() {
return new OioSocketChannel(new Socket(this.proxy));
}
}

View file

@ -1,6 +1,7 @@
package com.github.steveice10.packetlib.tcp;
import com.github.steveice10.packetlib.Client;
import com.github.steveice10.packetlib.ProxyInfo;
import com.github.steveice10.packetlib.packet.PacketProtocol;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
@ -10,21 +11,22 @@ import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.proxy.Socks4ProxyHandler;
import io.netty.handler.proxy.Socks5ProxyHandler;
import io.netty.util.concurrent.Future;
import javax.naming.directory.InitialDirContext;
import java.net.Proxy;
import java.util.Hashtable;
public class TcpClientSession extends TcpSession {
private Client client;
private Proxy proxy;
private ProxyInfo proxy;
private EventLoopGroup group;
public TcpClientSession(String host, int port, PacketProtocol protocol, Client client, Proxy proxy) {
public TcpClientSession(String host, int port, PacketProtocol protocol, Client client, ProxyInfo proxy) {
super(host, port, protocol);
this.client = client;
this.proxy = proxy;
@ -39,15 +41,10 @@ public class TcpClientSession extends TcpSession {
}
try {
final Bootstrap bootstrap = new Bootstrap();
if(this.proxy != null) {
this.group = new OioEventLoopGroup();
bootstrap.channelFactory(new ProxyOioChannelFactory(this.proxy));
} else {
this.group = new NioEventLoopGroup();
bootstrap.channel(NioSocketChannel.class);
}
this.group = new NioEventLoopGroup();
final Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel channel) throws Exception {
@ -61,6 +58,37 @@ public class TcpClientSession extends TcpSession {
refreshReadTimeoutHandler(channel);
refreshWriteTimeoutHandler(channel);
if(proxy != null) {
switch(proxy.getType()) {
case HTTP:
if(proxy.isAuthenticated()) {
pipeline.addFirst("proxy", new HttpProxyHandler(proxy.getAddress(), proxy.getUsername(), proxy.getPassword()));
} else {
pipeline.addFirst("proxy", new HttpProxyHandler(proxy.getAddress()));
}
break;
case SOCKS4:
if(proxy.isAuthenticated()) {
pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.getAddress(), proxy.getUsername()));
} else {
pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.getAddress()));
}
break;
case SOCKS5:
if(proxy.isAuthenticated()) {
pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.getAddress(), proxy.getUsername(), proxy.getPassword()));
} else {
pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.getAddress()));
}
break;
default:
throw new UnsupportedOperationException("Unsupported proxy type: " + proxy.getType());
}
}
pipeline.addLast("encryption", new TcpPacketEncryptor(TcpClientSession.this));
pipeline.addLast("sizer", new TcpPacketSizer(TcpClientSession.this));
pipeline.addLast("codec", new TcpPacketCodec(TcpClientSession.this));
@ -117,17 +145,10 @@ public class TcpClientSession extends TcpSession {
}
@Override
public void disconnect(String reason, Throwable cause, boolean wait) {
super.disconnect(reason, cause, wait);
public void disconnect(String reason, Throwable cause) {
super.disconnect(reason, cause);
if(this.group != null) {
Future<?> future = this.group.shutdownGracefully();
if(wait) {
try {
future.await();
} catch(InterruptedException e) {
}
}
this.group.shutdownGracefully();
this.group = null;
}
}

View file

@ -104,9 +104,14 @@ public abstract class TcpSession extends SimpleChannelInboundHandler<Packet> imp
@SuppressWarnings("unchecked")
@Override
public <T> T getFlag(String key) {
return this.getFlag(key, null);
}
@Override
public <T> T getFlag(String key, T def) {
Object value = this.getFlags().get(key);
if(value == null) {
return null;
return def;
}
try {
@ -214,8 +219,7 @@ public abstract class TcpSession extends SimpleChannelInboundHandler<Packet> imp
if(!sendingEvent.isCancelled()) {
final Packet toSend = sendingEvent.getPacket();
ChannelFuture future = this.channel.writeAndFlush(toSend).addListener(new ChannelFutureListener() {
this.channel.writeAndFlush(toSend).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
@ -225,33 +229,16 @@ public abstract class TcpSession extends SimpleChannelInboundHandler<Packet> imp
}
}
});
if(toSend.isPriority()) {
try {
future.await();
} catch(InterruptedException e) {
}
}
}
}
@Override
public void disconnect(String reason) {
this.disconnect(reason, false);
}
@Override
public void disconnect(String reason, boolean wait) {
this.disconnect(reason, null, wait);
this.disconnect(reason, null);
}
@Override
public void disconnect(final String reason, final Throwable cause) {
this.disconnect(reason, cause, false);
}
@Override
public void disconnect(final String reason, final Throwable cause, boolean wait) {
if(this.disconnected) {
return;
}
@ -265,19 +252,12 @@ public abstract class TcpSession extends SimpleChannelInboundHandler<Packet> imp
if(this.channel != null && this.channel.isOpen()) {
this.callEvent(new DisconnectingEvent(this, reason, cause));
ChannelFuture future = this.channel.flush().close().addListener(new ChannelFutureListener() {
this.channel.flush().close().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
callEvent(new DisconnectedEvent(TcpSession.this, reason != null ? reason : "Connection closed.", cause));
}
});
if(wait) {
try {
future.await();
} catch(InterruptedException e) {
}
}
} else {
this.callEvent(new DisconnectedEvent(this, reason != null ? reason : "Connection closed.", cause));
}

View file

@ -2,22 +2,21 @@ package com.github.steveice10.packetlib.tcp;
import com.github.steveice10.packetlib.Client;
import com.github.steveice10.packetlib.ConnectionListener;
import com.github.steveice10.packetlib.ProxyInfo;
import com.github.steveice10.packetlib.Server;
import com.github.steveice10.packetlib.Session;
import com.github.steveice10.packetlib.SessionFactory;
import java.net.Proxy;
/**
* A session factory used to create TCP sessions.
*/
public class TcpSessionFactory implements SessionFactory {
private Proxy clientProxy;
private ProxyInfo clientProxy;
public TcpSessionFactory() {
}
public TcpSessionFactory(Proxy clientProxy) {
public TcpSessionFactory(ProxyInfo clientProxy) {
this.clientProxy = clientProxy;
}