介绍上一篇我们讲到netty提供SOCKS消息,封装了SocksMessage对象,区分SOCKS4和SOCKS5,同时提供连接和响应时间各种状态。封装完SOCKS消息之后,我们还需要做些什么来搭建一个SOCKS服务器呢?使用SSH搭建SOCKS服务器最简单的方法就是使用SSH工具搭建SOCKS代理服务器。先看SSH建立SOCKS服务的命令:ssh-f-C-N-Dbindaddress:portname@server-f表示SSH作为守护进程进入后台执行。-N表示不执行远程命令,只做端口转发。-D表示在端口上动态转发。此命令支持SOCKS4和SOCKS5。-C表示发送前压缩数据。bindaddress本地服务器的绑定地址。port表示本地服务器的指定监听端口。name表示ssh服务器登录名。server表示ssh服务器地址。上面命令的意思是在本机建立端口绑定,然后转发给远程代理服务器。比如我们可以在本机打开一个2000端口,转发给远程机器168.121.100.23:ssh-f-N-D0.0.0.0:2000root@168.121.100.23有了代理服务器后,就可以使用了,首先介绍如何在curl命令中使用SOCKS代理。我们想通过代理服务器访问www.flydean.com,怎么办?curl-xsocks5h://localhost:2000-v-k-XGEThttp://www.flydean.com:80检测SOCKS连接也可以使用netcat命令如下:ncat–proxy127.0.0.1:2000–proxy-typesocks5www.flydean.com80-nv使用netty搭建SOCKS服务器使用netty搭建SOCKS服务器的关键是把netty服务器作为中继,需要建立两个连接,一个一是客户端到代理服务器的连接,一是代理服务器到目标地址的连接。下面我们一步一步来探索如何在netty中搭建一个SOCKS服务器。搭建服务器的基本步骤与普通服务器基本相同,在消息读取过程中需要注意消息的编码、解码和转发。Encoder和decoder对于一个协议来说,最终要求的是对应的encoder和decoder,用于协议对象和ByteBuf之间的转换。netty提供的SOCKS转换器叫做SocksPortUnificationServerHandler。先看它的定义:publicclassSocksPortUnificationServerHandlerextendsByteToMessageDecoder,它继承自ByteToMessageDecoder,意思是它是ByteBuf和Socks对象之间的转换。所以我们只需要在ChannelInitializer中添加SocksPortUnificationServerHandler和自定义的Socks消息处理器:;}等等,那是不对的!细心的朋友可能已经发现,SocksPortUnificationServerHandler只是一个解码器,我们还缺少一个编码器来将Socks对象转换成ByteBuf。这个编码器在哪里?别担心,让我们回到SocksPortUnificationServerHandler。在它的decode方法中,有这么一段代码:caseSOCKS4a:logKnownVersion(ctx,version);p.addAfter(ctx.name(),null,Socks4ServerEncoder.INSTANCE);p.addAfter(ctx.name(),null,newSocks4ServerDecoder());休息;案例SOCKS5:logKnownVersion(ctx,version);p.addAfter(ctx.name(),null,socks5encoder);p.addAfter(ctx.name(),null,newSocks5InitialRequestDecoder());休息;原来是在decode方法里,按照Socks5ocks的版本不同,在ctx中添加了相应的encoder和decoder。非常巧妙的对应编码器分别是Socks4ServerEncoder和Socks5ServerEncoder。建立连接对于Socks4,只有一种建立连接的请求类型,在netty中用Socks4CommandRequest表示。所以我们只需要判断channelRead0中请求的版本即可:caseSOCKS4a:Socks4CommandRequestsocksV4CmdRequest=(Socks4CommandRequest)socksRequest;if(socksV4CmdRequest.type()==Socks4CommandType.CONNECT){ctx.pipeline().addLast(newSocksServerConnectHandler());ctx.pipeline().remove(这个);ctx.fireChannelRead(socksRequest);}else{ctx.close();}这里我们添加一个自定义的SocksServerConnectHandler来处理Socks连接,这个自定义的handler后面会详细解释,这里我们都知道可以用来建立连接。对于Socks5来说,比较复杂,包括初始化请求、鉴权请求和连接建立,所以需要单独处理:ctx.write(新的DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH));}elseif(socksRequestinstanceofSocks5PasswordAuthRequest){ctx.pipeline().addFirst(newSocks5CommandRequestDecoder());ctx.write(newDefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.SUCCESS));}elseif(socksRequestinstanceofSocks5CommandRequest){Socks5CommandRequestsocks5CmdRequest=(Socks5CommandRequest)socksRequest;如果(socks5CmdRequest.type()==Socks5CommandType.CONNECT){ctx.().删除(这个);ctx.fireChannelRead(socksRequest);}else{ctx.close();注意,我们这里的认证请求只支持用户名密码认证。由于ConnectHandler作为代理服务器,需要建立两个连接,一个是客户端到代理服务器的连接,一个是代理服务器到目标服务器的连接。对于netty来说,这两个连接可以用两个Bootstrap来建立。其中,我们在启动netty服务器的时候已经建立了客户端到代理服务器的连接,所以我们需要在ConnectHandler中重新建立代理服务器到目标服务器的连接:privatefinalBootstrapb=newBootstrap();通道inboundChannel=ctx.channel();b.group(inboundChannel.eventLoop()).channel(NioSocketChannel.class).option(ChannelOption.CONNECT_TIMEOUT_MILLIS,10000).option(ChannelOption.SO_KEEPALIVE,true).handler(newClientPromiseHandler(promise));b.connect(request.dstAddr(),request.dstPort()).addListener(future->{if(future.isSuccess()){//成功建立连接}else{//关闭连接ctx.channel().writeAndFlush(newDefaultSocks4CommandResponse(Socks4CommandStatus.REJECTED_OR_FAILED));closeOnFlush(ctx.channel());}});新的Bootstrap需要从接收到的Socks报文中提取出目标服务器的地址和端口,然后建立连接然后判断新建立的连接的状态。如果成功,则添加一个forwarder将outboundChannel消息转发到inboundChannel,同时将inboundChannel消息转发到outboundChannel,从而达到服务器代理的目的。最终通道outboundChannel=future.getNow();如果(future.isSuccess()){ChannelFutureresponseFuture=ctx.channel().writeAndFlush(newDefaultSocks4CommandResponse(Socks4CommandStatus.SUCCESS));//成功建立连接,删除SocksServerConnectHandler,添加RelayHandlerresponseFuture.addListener(channelFuture->{ctx.pipeline().remove(SocksServerConnectHandler.this);outboundChannel.pipeline().addLast(newRelayHandler(ctx.channel()));ctx.pipeline().addLast(newRelayHandler(outboundChannel));});}else{ctx.channel().writeAndFlush(newDefaultSocks4CommandResponse(Socks4CommandStatus.REJECTED_OR_FAILED));closeOnFlush(ctx.channel());}总结说白了,代理服务器就是建立两个连接,把一个连接的消息转发给另一个。这个操作在netty中非常简单。本文示例可参考:learn-netty4本文已收录于http://www.flydean.com/37-netty-cust-socks-server/最通俗的解读,最深刻的干货,最简洁的教程,许多你不为人知的小技巧等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!
