Using Netty to build a SOCKS proxy
Recently in the project, you need to build a socks agent. Netty4.0 comes with an example of a SOCKS proxy, but 3.x does not have this thing, it happens to use 3.7, it can only be explored and realized once, also is a familiar to Netty and socks agreement. Socks agent involves protocol resolution, server, client and other functions, is a more complex network program, for learning Netty use is also a very good example.
Socks is a layer of protocol on the Transport layer, the main function is to provide proxy authentication and other functions. Although the SOCKS protocol is an application-layer protocol (in the TCP/IP4 Layer protocol stack), it can be understood as a channel that can transmit any TCP/UDP content. For example, the famous science Internet software is based on the SOCKS protocol, the communication Content encryption implementation.
In the structure of the TCP/IP protocol stack, the lower protocol always adds its own head before the upper-level protocol content. and the SOCKS protocol is slightly different, in fact, it compared to the TCP protocol, just a lot of verification, after verification, completely using TCP for transmission, and no socks headers. The specific contents of the SOCKS agreement can be referred to rfc1928. In this regard, it is not a problem to think of socks as a peer agreement with other application layers.
One of the most basic socks connection processes is this:
So let's start the Netty tour.
First we need to build a server:
public void run () {//new thread pool Executor Executor = Executors.newcachedthreadpool (); Executor Executorworker = Executors.newcachedthreadpool (); Serverbootstrap sb = new serverbootstrap (new Nioserversocketchannelfactory (executor, executorworker)); //initialize proxy section using client clientsocketchannelfactory CF = new Nioclientsocketchannelfactory (executor, executorworker); //Set processing logic sb.setpipelinefactory (new Socksproxypipelinefactory (CF)); //Start up the server. Sb.bind (new inetsocketaddress (1080)}
As you can see, the main processing logic is provided in the form of socksproxypipelinefactory. The Socksproxypipelinefactory code consists of several parts:
PublicClassSocksproxypipelinefactoryImplementschannelpipelinefactory {PrivateFinal Clientsocketchannelfactory CF;public Socksproxypipelinefactory (Clientsocketchannelfactory CF) { THIS.CF = CF; } @Override public Channelpipeline getpipeline () Throws Exception {Channelpipeline pipeline = Channels.pipeline (); Pipeline.addlast (Socksinitrequestdecoder.getname () , new Socksinitrequestdecoder ()); Pipeline.addlast (Socksmessageencoder.getname (), new Socksmessageencoder ()); Pipeline.addlast (Socksserverhandler.getname (), new Socksserverhandler (CF)); return pipeline; }}
Here is a detailed explanation of the role of several handler:
ChannelUpstreamHandler
Used for processing after receiving, and ChannelDownstreamHandler
vice versa, after the data is written. Both of these can be attached to ChannelPipeline
. Steal a lazy, directly attached to Netty's channelpipeline in a very loving Javadoc:
I/O Request via {@link Channel} or {@link Channelhandler Context}| +----------------------------------------+---------------+| Channelpipeline | || \|/ || +----------------------+ +-----------+------------+ || | Upstream Handler N | | Downstream Handler 1 | || +----------+-----------+ +-----------+------------+ || /|\ | || | \|/ || +----------+-----------+ +-----------+------------+ || | Upstream Handler N-1 | | Downstream Handler 2 | || +----------+-----------+ +-----------+------------+ || /|\ . || . . || [Sendupstream ()] [Senddownstream ()] || [+ INBOUND Data] [+ OUTBOUND Data] || . . || . \|/ || +----------+-----------+ +-----------+------------+ || | Upstream Handler 2 | | Downstream Handler M-1 | || +----------+-----------+ +-----------+------------+ | |/|\ | | | | \|/| | +----------+-----------+ +-----------+------------+ | | | Upstream Handler 1 | | Downstream Handler M | | | +----------+-----------+ +-----------+------------+ | |/|\ | | +-------------+--------------------------+---------------+ | \|/+-------------+--------------------------+---------------+ | | | | | [Socket.read ()] [Socket.write ()] | | | | Netty Internal I/O Threads (Transport implementation) | +--------------------------------------------------------+
SocksInitRequestDecoder
Used to decode the socks request. You might say, why not sockscmdrequest decoding? Don't worry, Netty's handler can be added dynamically, and here we decode an initialized request first. The Socksinitrequestdecoder is a ChannelUpstreamHandler
processor that receives a stream.
SocksMessageEncoder
is a ChannelDownstreamHandler
, the output of the encoder, with it, we can happily in the channel.write () directly into an object, without having to write buffer itself.
SocksServerHandler
is to deal with it all over again. Depending on the type of request, different processing is done here.
PublicvoidMessagereceived(Channelhandlercontext ctx, messageevent e)Throws Exception {socksrequest socksrequest = (socksrequest) e.getmessage ();Switch (Socksrequest.getsocksrequesttype ()) {Case INIT://Add cmd decoder ctx.getpipeline (). AddFirst (Sockscmdrequestdecoder.getname (),New Sockscmdrequestdecoder ());For simplicity, no certification ctx.getchannel (). Write (New Socksinitresponse (SocksMessage.AuthScheme.NO_AUTH));BreakCase AUTH:ctx.getPipeline (). AddFirst (Sockscmdrequestdecoder.getname (), new Sockscmdrequestdecoder ()); //Direct Success Ctx.getchannel (). Write (new socksauthresponse ( SocksMessage.AuthStatus.SUCCESS)); break; case cmd:sockscmdrequest req = (sockscmdrequest) socksrequest; if (req.getcmdtype () = = SocksMessage.CmdType.CONNECT) {// Add Handler Ctx.getpipeline () that handles the connection. AddLast (Socksserverconnecthandler.getname (), new Socksserverconnecthandler (CF)); Ctx.getpipeline (). Remove (this);} else {ctx.getchannel (). Close ();} break; case UNKNOWN: break;} super.messagereceived (CTX, E);}
The first two kinds of init and auth do not repeat, after the cmd for Connect, add a processing connection SocksServerConnectHandler
, it will play the role of the client and external server bridge.
Here we first implement a pure forwarding handler- OutboundHandler
:
PrivateClassOutboundhandlerextends Simplechannelupstreamhandler {private final channel Inboundchannel; outboundhandler (channel Inboundchannel) { this.inboundchannel = Inboundchannel;} @Override public void messagereceived (channelhandlercontext CTX, final messageevent e) throws Span class= "Hljs-type" >exception {final channelbuffer msg = ( Span class= "Hljs-type" >channelbuffer) e.getmessage (); Synchronized (Trafficlock) {inboundchannel.write (msg);}}}
It will send the received content, write to the role of the inboundChannel
other forwards. The end is ours SocksServerConnectHandler
:
PublicClassSocksserverconnecthandlerExtendsSimplechannelupstreamhandler {PrivateFinal Clientsocketchannelfactory CF;PrivateVolatile Channel Outboundchannel;Final Object Trafficlock =New Object ();PublicSocksserverconnecthandler(Clientsocketchannelfactory CF) {THIS.CF = CF; }@OverridePublicvoidMessagereceived(Channelhandlercontext ctx, messageevent e)Throws Exception {Final Sockscmdrequest sockscmdrequest = (sockscmdrequest) e.getmessage ();Final Channel Inboundchannel = E.getchannel (); Inboundchannel.setreadable (FALSE);Start the connection attempt.Final Clientbootstrap cb =New Clientbootstrap (CF); Cb.setoption ("KeepAlive",true); Cb.setoption ("Tcpnodelay",true); Cb.setpipelinefactory (New Channelpipelinefactory () {@OverridePublic ChannelpipelineGetpipeline()Throws Exception {Channelpipeline pipeline = Channels.pipeline ();External server data is forwarded to client Pipeline.addlast ("Outboundchannel",New Outboundhandler (Inboundchannel,"Out"));return pipeline; } }); Channelfuture f = cb.connect (New Inetsocketaddress (Sockscmdrequest.gethost (), Sockscmdrequest.getport ())); Outboundchannel = F.getchannel (); Ctx.getpipeline (). Remove (GetName ()); F.addlistener (New Channelfuturelistener () {public void operationComplete< Span class= "Hljs-params" > (channelfuture future) throws Exception { if (future.issuccess ()) {//client data forwarded to external server Inboundchannel.getpipeline (). AddLast (new Outboundhandler (Outboundchannel, "in")); Inboundchannel.write (new sockscmdresponse (SocksMessage.CmdStatus.SUCCESS, Sockscmdrequest. Getaddresstype ())); Inboundchannel.setreadable (true);} else {inboundchannel.write (new SocksCmdResponse ( SocksMessage.CmdStatus.FAILURE, Sockscmdrequest. Getaddresstype ())); Inboundchannel.close (); } } }); }}
All right, it's finished! Input curl --socks5 127.0.0.1:1080 http://www.oschina.net/
test it? But the test found that the response is always unable to receive?
After using Wiredshark to catch the packet, it is found that the external request is completely normal, but the response to the client, there is no HTTP response part at all?
Step by step debug down, only to find SocksMessageEncoder
out the problem!
@Overrideprotected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { ChannelBuffer buffer = null; if (msg instanceof SocksMessage) { buffer = ChannelBuffers.buffer(DEFAULT_ENCODER_BUFFER_SIZE); ((SocksMessage) msg).encodeAsByteBuf(buffer); } return buffer;}
Only socksmessage will be processed, and all the other message has been discarded! So we add a line:
@Override protected Span class= "hljs-function" >object encode ( Channelhandlercontext CTX, channel Channel, Object msg) throws Exception {Channelbuffer Buffer = null; if (msg instanceof socksmessage) {buffer = Channelbuffers.buffer (default_encoder_buffer_size); ((socksmessage) msg). Encodeasbytebuf (buffer); } else if (msg instanceof channelbuffer) {/ /Direct forwarding is channelbuffer type buffer = (channelbuffer) msg; } return buffer;
At this point, an agent is complete! Click here to view the code: Https://github.com/code4craft/netty-learning/tree/master/learning-src/socksproxy
Using Netty to build a SOCKS proxy