Using ChannelDuplexHandler to connect to another server

319 views
Skip to first unread message

L_DisplayName

unread,
Sep 6, 2016, 12:59:22 PM9/6/16
to Netty discussions
Greetings All,
  I wanted to know how I can use ChannelDuplexHandler from a server to connect to another server? Also I would like to know if my understanding of channelDuplex handler is correct and what's the difference between bootstrapping a client and having it connect to a server verses having the ChannelDuplexHandler connect to the server?

  For example: If I have a client A, a Server B and a Server C.  and I want client A to connect to Server B and Server B connect to Server C.  where pictorially  I have:
  A-->B-->C 

Below I show Server's B Handler code, as soon as client A connects to Server B (The code for this is below B's Server Handler code), I want Server B to connect to Server C, can you tell me if the code below is correct or if any thing needs  to be changed and if so what and how? Thank you!

Attempting to connect Server B to Server C using ChannelDuplexHandler (Note I am showing Server B's handler class)
public class Server_B_Handler extends ChannelDuplexHandler {

 
private String ipAddressToConnectTo, localIpAddress;
 
private int portToConnectTo, localPort;
 
private Logger logger;
 
private ChannelHandlerContext ctx;
 
private Channel channelFrom_A_to_B;
 
private Channel channelFrom_B_to_C;

 
public Server_B_Handler(String ipAddress, int port, localIpAddress, localPort) {
  ipAddressToConnectTo
= ipAddress;
 portToConnectTo
= port
 
this.localIpAddress = localIpAddress;
 
this.localPort = localPort;
 logger
= Logger.getLogger(this.getClass().getName());
 
this.ctx = null;
 channelFrom_A_to_B
= null; //Inbound channel
 channelFrom_B_to_C
= null; //Outbound Channel
 
 
}

 
@Override
 
public void channelActive(ChannelHandlerContext ctx) throws Exception{
 
//THIS CTX (Channel context) is for the channel connecting Client A to this Server (Server B)
 
//Please see below for code connecting client A to Server B
 
this.ctx = ctx;
 channelFrom_A_to_B
= this.ctx().channel();
 
 
//QUESTION HOW CAN I CONNECT TO SERVER C using this server's CONNECT METHOD?
 
 
final Promise<Channel> myPromise = this.ctx.executor().newPromise();
 ctx
.connect(new InetSocketAddress(InetAddress.getByName(ipAddressToConnectTo), portToConnectTo),new InetSocketAddress(this.localIpAddress,this.localPort),myPromise);
 myPromise
.addListener(new FutureListener<Channel>(){
 
@override
 
public void operationComplete(final Future<Channel> future) throws Exception {
 
if (future.isSuccess()){
 
//Connection attempt successful
 channelFrom_B_to_C
= future.get();
 logger
.info("Server B successfully connected to Server C")
 
}
 
else {
 
//Connection attempt was not successful
 channelFrom_B_to_C
= future.get().close();
 logger
.info("Server B did not successfully connect to Server C. closing...")
 
}
 
});
 
 
}


 
@Override
 
public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
 
//What ever the inbound channel (channelFrom_A_to_B) reads write it to the outbound channel
 
if ( channelFrom_B_to_C.isActive()){
 channelFrom_B_to_C
.write(msg);
 
}
 
}

 
@Override
 
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
 cause
.printStackTrace();

 
if (ctx.channel().isActive()) {
 ctx
.writeAndFlush("ERR: " +
 cause
.getClass().getSimpleName() + ": " +
 cause
.getMessage() + '\n').addListener(ChannelFutureListener.CLOSE);
 
}
 
}
}


I know I can connect Client A to Client B by the following code
public final class Client_A {

 
static final boolean SSL = System.getProperty("ssl") != null;
 
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
 
static final String HOST = System.getProperty("host", "129.456.7.9");
 
static final int PORT = Integer.parseInt(System.getProperty("port","4959"));

 
public static void main(String[] args) throws Exception {
 
final SslContext sslCtx;
 
if (SSL) {
 sslCtx
= SslContextBuilder.forClient()
 
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
 
} else {
 sslCtx
= null;
 
}

 
// Configure the client.
 
EventLoopGroup group = new NioEventLoopGroup();
 
try {
 
Bootstrap b = new Bootstrap();
 b
.group(group)
 
.channel(NioSocketChannel.class)
 
.option(ChannelOption.TCP_NODELAY, true)
 
.handler(new ChannelInitializer<SocketChannel>() {
 
@Override
 
public void initChannel(SocketChannel ch) throws Exception {
 
ChannelPipeline p = ch.pipeline();
 
if (sslCtx != null) {
 p
.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
 
}
 
//p.addLast(new LoggingHandler(LogLevel.INFO));
 p
.addLast(
 
new LengthFieldBasedFrameDecoder(1024*1024*128, 0, 8, 0, 8),
 
new Client_A_Handler());
 
}
 
});

 
// Start the client.
 
ChannelFuture f = b.connect(HOST, PORT).sync();

 
// Wait until the connection is closed.
 f
.channel().closeFuture().sync();
 
} finally {
 
// Shut down the event loop to terminate all threads.
 
group.shutdownGracefully();
 
}
 
}
}



Norman Maurer

unread,
Sep 6, 2016, 3:26:30 PM9/6/16
to ne...@googlegroups.com
Just check our hex dump example… This does exactly what you want to do:


--
You received this message because you are subscribed to the Google Groups "Netty discussions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to netty+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/netty/cc201da0-7cac-4534-a666-6ac8efa9d677%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

L_DisplayName

unread,
Sep 6, 2016, 4:48:19 PM9/6/16
to Netty discussions
Thank you Norman, 
  That was the first thing I did, however HexDumpProxy doesn't use ChannelDuplexHandler , HexDumpProxyFrontendHandler extends ChannelInboundHandlerAdapter and so does the HexDumpProxyBackendHandler. In addition, the socksProxy classes doesn't utilize channelDuplexHandler. I do see the functionality I was aiming for, however I could not find any examples of how to utilize the ChannelDuplexHandler.

But, Again, I appreciate your response

loolo...@gmail.com

unread,
Sep 20, 2016, 5:12:28 PM9/20/16
to Netty discussions
What you are missing in your code is the Relay, the part passing bytes to and from Client-A and Server-C. The HexDumpProxy's implementation of the relay is in two classes: HexDumpProxyBackendHandler and HexDumpProxyFrontendHandler
After the line  logger.info("Server B successfully connected to Server C"you would want to do what HexDumpProxy does (adding the relay handlers): HexDumpProxyInitializer.java - Line 34

The HexDump uses two ChannelInboundHandlerAdapter handlers, This is because it does not make sense to use a duplex handler. You have 2 channels, and you want to pass (write) the bytes just read into the other channel. Nothing to be done in case of a write event (and thus the write method of duplex handler never used).

If you need to do any processing on the data both read and write in the client's channel, there is a better way: forget the fact that there is a channelFrom_B_to_C. This second channel, managing it's state, handing exceptions or anything else is responsibility of Relay, and a relay is a simple passive handler, it does NOT do any processing. 
Now, in the channelFrom_A_to_B add a handler, let's call it MitmHandler (Man in the middle handler).

MitmHandler is a duplex handler, something like this:

@Slf4j
public class MitmHandler extends ChannelDuplexHandler {

    @Override
    public void channelRead(final ChannelHandlerContext ctx,
                            final Object msg) throws Exception {

        if (msg instanceof ByteBuf) {
            final ByteBuf b = (ByteBuf) msg;
            for (int i = b.readerIndex(); i < b.readableBytes(); i++)
                b.setByte(i, (byte) b.getByte(i) + 1);
        }

        super.channelRead(ctx, msg);
    }

    @Override
    public void write(final ChannelHandlerContext ctx,
                      final Object msg,
                      final ChannelPromise promise) throws Exception {

        if (msg instanceof ByteBuf) {
            final ByteBuf b = (ByteBuf) msg;
            for (int i = b.readerIndex(); i < b.readableBytes(); i++)
                b.setByte(i, (byte) b.getByte(i) - 1);
        }

        super.write(ctx, msg, promise);
    }
}

Just a dummy (crazy) handler, subtracts 1 from each byte in write direction (from C to A / Outbound), add 1 to each byte in read direction (from A to C / Inbound). You could add it to channelFrom_B_to_C but it just makes things complex, channelFrom_B_to_C is a dependency (?) of channelFrom_A_to_B, I suggest to call them master and slave, and do things in the master and forget the slave.

One thing about auto read: DO NOT FORGET to set auto read of BOTH channels to false, and manually initiate a read in your relay. The HexDumpProxy shows how to do it.

These were source of confusion for me for a long while. hope it helps.
Reply all
Reply to author
Forward
0 new messages