Netty实现shadowsocks客户端
来源:互联网 发布:淘宝开店需要电脑吗 编辑:程序博客网 时间:2024/04/29 18:16
shadowsocks是什么
shadowsocks是基于socks5协议实现的代理方式,分为服务器和客户端,双端之间通过使用指定的加密方式(AES,BlowFish,Table等)进行数据传输,有效的突破了GFW。
整个流程可以看来自网上的一张流程图:
总体流程大致如上图所示,具体实现细节后面会介绍,今天要实现的是SS Local这个模块,数据传输都是基于socks5协议,所以有必要先了解一下socks5协议。
socks5协议
SOCKS5 是一个代理协议,它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网中的前端机器变得能够访问Internet网中的服务器,或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器, 模拟了一个前端的行为。
1.建立与SOCKS5服务器的TCP连接后客户端需要先发送请求来协商版本及认证方式,格式为(以字节为单位):
+-----+----------+-----------+| VER | NMETHODS | METHODS |+-----+----------+-----------+| 1 | 1 | 1 to 255 |+-----+----------+-----------+
VER是SOCKS版本,这里应该是0x05;
NMETHODS是METHODS部分的长度;
METHODS是客户端支持的认证方式列表,每个方法占1字节。当前的定义是:
0x00 不需要认证0x01 GSSAPI0x02 用户名、密码认证0x03 - 0x7F由IANA分配(保留)0x80 - 0xFE为私人方法保留0xFF 无可接受的方法2.服务器从客户端提供的方法中选择一个并通过以下消息通知客户端(以字节为单位):
+-----+---------+| VER | NMETHOD |+-----+---------+| 1 | 1 |+-----+---------+
VER是SOCKS版本,这里应该是0x05;
METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,客户端需要关闭连接。
之后客户端和服务端根据选定的认证方式执行对应的认证。
3.认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装。
SOCKS5请求格式(以字节为单位):
+-----+-----+-------+------+----------+----------+| VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |+-----+-----+-------+------+----------+----------+| 1 | 1 | X'00' | 1 | Variable | 2 |+-----+-----+-------+------+----------+----------+
VER是SOCKS版本,这里应该是0x05;
CMD是SOCK的命令码
0x01表示CONNECT请求0x02表示BIND请求0x03表示UDP转发
RSV 0x00,保留
ATYP 后面的地址类型
0x01 IPv4地址,DST ADDR部分4字节长度0x03 域名,DST ADDR部分第一个字节为域名长度,DST ADDR剩余的内容为域名,没有\0结尾。0x04 IPv6地址,16个字节长度。
DST.ADDR 目的地址
DST.PROT 目的端口
4.服务器按以下格式回应客户端的请求(以字节为单位):
+-----+-----+-------+------+----------+----------+| VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |+-----+-----+-------+------+----------+----------+| 1 | 1 | X'00' | 1 | Variable | 2 |+-----+-----+-------+------+----------+----------+REP 应答字段
0x00 表示成功0x01 普通SOCKS服务器连接失败0x02 现有规则不允许连接0x03 网络不可达0x04 主机不可达0x05 连接被拒0x06 TTL超时0x07 不支持的命令0x08 不支持的地址类型0x09 0xFF未定义
BND.ADDR 服务器绑定的地址
BND.PORT 以网络字节顺序表示的服务器绑定的端口
更多的socks5介绍rfc1928
四次握手结束之后,client 会把代理服务器当作是自己真正想通讯的服务器一样对待,这时代理服务器才会转发真正的数据。
具体实现
根据上面整体的流程图以及对socks5的了解,具体实现整理了如下几步:
1.pc向ss local发送请求来协商版本和认证方法,ss local回复pc2.pc发送详细的请求,并且指定的cmdType为connectss local处理cmdType为connect的请求;此时ss local和ss server建立连接,如果连接成功ss local回复pc为成功否则为失败;如果成功ss local发送请求给ss server,发送的内容是pc发送给ss local的内容跳过前面的3个字节3.握手成功后需要为pc和ss local建立的channel添加消息处理器,用来接受pc的请求,并进行加密转发给ss server;同时为ss local和ss server建立的channle添加消息处理器,用来接受ss server的请求,并进行解密转发给pc4.配置浏览器,进行测试因为netty的高性能以及4.0以后对socks5协议的支持,这里选择了:netty4.0.41
<span style="color:#333333;"><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.0.41.Final</version></dependency></span>
以下代码参考了netty4.0.41自带的example中的socksproxy
第一步:处理协商版本和认证方法
因为netty4.0.x对socks5协议的支持,直接使用SocksInitRequestDecoder和SocksMessageEncoder
public final class SocksServerInitializer extendsChannelInitializer<SocketChannel> {private SocksMessageEncoder socksMessageEncoder;private SocksServerHandler socksServerHandler;public SocksServerInitializer(Config config) {socksMessageEncoder = new SocksMessageEncoder();socksServerHandler = new SocksServerHandler(config);}@Overridepublic void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline p = socketChannel.pipeline();p.addLast(new SocksInitRequestDecoder());p.addLast(socksMessageEncoder);p.addLast(socksServerHandler);}}经过SocksInitRequestDecoder的解码,数据流转到自定义的SocksServerHandler中
@ChannelHandler.Sharablepublic final class SocksServerHandler extendsSimpleChannelInboundHandler<SocksRequest> {private static Log logger = LogFactory.getLog(SocksServerHandler.class);private Config config;public SocksServerHandler(Config config) {this.config = config;}@Overridepublic void channelRead0(ChannelHandlerContext ctx,SocksRequest socksRequest) throws Exception {switch (socksRequest.requestType()) {case INIT: {logger.info("localserver init");ctx.pipeline().addFirst(new SocksCmdRequestDecoder());ctx.write(new SocksInitResponse(SocksAuthScheme.NO_AUTH));break;}case AUTH:ctx.pipeline().addFirst(new SocksCmdRequestDecoder());ctx.write(new SocksAuthResponse(SocksAuthStatus.SUCCESS));break;case CMD:SocksCmdRequest req = (SocksCmdRequest) socksRequest;if (req.cmdType() == SocksCmdType.CONNECT) {logger.info("localserver connect");ctx.pipeline().addLast(new SocksServerConnectHandler(config));ctx.pipeline().remove(this);ctx.fireChannelRead(socksRequest);} else {ctx.close();}break;case UNKNOWN:ctx.close();break;}}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {throwable.printStackTrace();SocksServerUtils.closeOnFlush(ctx.channel());}}
对pc发送的协议版本和认证方法,SocksInitRequestDecoder将它解码为请求类型为INIT,pc和ss local的连接我们是不需要验证的,所有这里返回的是SocksAuthScheme.NO_AUTH,十六进制是:0x00,可以对照前面介绍的socks5协议。同时也添加了SocksCmdRequestDecoder,用来解码接下来的请求协议。
第二步:处理详细的请求,完成握手
经过SocksCmdRequestDecoder的解码,请求类型变成了CMD,同时处理cmdType为connect;此时添加了SocksServerConnectHandler,并将SocksServerHandler移除,这样握手成功后发送的数据都不经过SocksServerHandler了,交给SocksServerConnectHandler处理。
@ChannelHandler.Sharablepublic final class SocksServerConnectHandler extendsSimpleChannelInboundHandler<SocksCmdRequest> {private static Log logger = LogFactory.getLog(SocksServerConnectHandler.class);public static final int BUFFER_SIZE = 16384;private final Bootstrap b = new Bootstrap();private ICrypt _crypt;private ByteArrayOutputStream _remoteOutStream;private ByteArrayOutputStream _localOutStream;private Config config;public SocksServerConnectHandler(Config config) {this.config = config;this._crypt = CryptFactory.get(config.get_method(), config.get_password());this._remoteOutStream = new ByteArrayOutputStream(BUFFER_SIZE);this._localOutStream = new ByteArrayOutputStream(BUFFER_SIZE);}@Overridepublic void channelRead0(final ChannelHandlerContext ctx,final SocksCmdRequest request) throws Exception {Promise<Channel> promise = ctx.executor().newPromise();promise.addListener(new GenericFutureListener<Future<Channel>>() {@Overridepublic void operationComplete(final Future<Channel> future)throws Exception {final Channel outboundChannel = future.getNow();if (future.isSuccess()) {final InRelayHandler inRelay = new InRelayHandler(ctx.channel(), SocksServerConnectHandler.this);final OutRelayHandler outRelay = new OutRelayHandler(outboundChannel, SocksServerConnectHandler.this);ctx.channel().writeAndFlush(getSuccessResponse(request)).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) {sendConnectRemoteMessage(request, outboundChannel);ctx.pipeline().remove(SocksServerConnectHandler.this);outboundChannel.pipeline().addLast(inRelay);ctx.pipeline().addLast(outRelay);}});} else {ctx.channel().writeAndFlush(new SocksCmdResponse(SocksCmdStatus.FAILURE,request.addressType()));SocksServerUtils.closeOnFlush(ctx.channel());}}});final Channel inboundChannel = ctx.channel();b.group(inboundChannel.eventLoop()).channel(NioSocketChannel.class).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000).option(ChannelOption.SO_KEEPALIVE, true).handler(new DirectClientHandler(promise));b.connect(config.get_ipAddr(), config.get_port()).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future)throws Exception {if (!future.isSuccess()) {ctx.channel().writeAndFlush(getFailureResponse(request));SocksServerUtils.closeOnFlush(ctx.channel());}}});}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {SocksServerUtils.closeOnFlush(ctx.channel());}}首先ss local和ss server建立了连接,根据连接成功还是失败ss local回复pc,分别是SocksCmdStatus.SUCCESS(0x00)和SocksCmdStatus.FAILURE(0x01),并且在成功之后向ss server发送请求
private void sendConnectRemoteMessage(SocksCmdRequest request,Channel outboundChannel) {ByteBuf buff = Unpooled.directBuffer();request.encodeAsByteBuf(buff);if (!buff.hasArray()) {int len = buff.readableBytes();byte[] arr = new byte[len];buff.getBytes(0, arr);byte[] data = remoteByte(arr);sendRemote(data, data.length, outboundChannel);}}
请求其实就是pc发送给ss local的消息,只不过需要跳过前面的3个字节
public void sendRemote(byte[] data, int length, Channel channel) {_crypt.encrypt(data, length, _remoteOutStream);byte[] sendData = _remoteOutStream.toByteArray();channel.writeAndFlush(Unpooled.wrappedBuffer(sendData));logger.info("sendRemote message:length = " + length+",channel = " + channel);}
发送给ss server需要对消息进行加密_crypt.encrypt()
第三步:为2个channel添加消息处理器
2个channel分别是pc->ss local 和 ss local-> ss server
SocksServerConnectHandler中在和ss server连接成功之后移除了SocksServerConnectHandler,以后pc发送给ss local的消息都不在经过它,交给OutRelayHandler,同时为ss local-> ss server的channel添加了InRelayHandler处理器
public final class OutRelayHandler extends ChannelInboundHandlerAdapter {private final Channel relayChannel;private SocksServerConnectHandler connectHandler;public OutRelayHandler(Channel relayChannel,SocksServerConnectHandler connectHandler) {this.relayChannel = relayChannel;this.connectHandler = connectHandler;}@Overridepublic void channelActive(ChannelHandlerContext ctx) {ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {if (relayChannel.isActive()) {ByteBuf bytebuff = (ByteBuf) msg;if (!bytebuff.hasArray()) {int len = bytebuff.readableBytes();byte[] arr = new byte[len];bytebuff.getBytes(0, arr);connectHandler.sendRemote(arr, arr.length, relayChannel);}} else {ReferenceCountUtil.release(msg);}}@Overridepublic void channelInactive(ChannelHandlerContext ctx) {if (relayChannel.isActive()) {SocksServerUtils.closeOnFlush(relayChannel);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}}
OutRelayHandler接受到pc的消息之后,对消息加密,同时转发给ss server
InRelayHandler接受到pc的消息之后,对消息解密,同时转发给ss local
整个流程大概就是这样,以上代码参考了一些开源项目,比如加密解密那块
第四步:配置浏览器进行测试
chrome浏览器本身是不支持socks5的,需要安装Proxy SwitchySharp插件
Firefox浏览器本身是支持socks5的
配置:
指定socks5协议,指定ip为:127.0.0.1,端口为:配置文件中配置的localport,默认是1081
启动local server:
指定浏览器的代理配置:
正常打开Google,Facebook,并且流畅的看完了一集youtube上的视频
源码:
github:https://github.com/ksfzhaohui/shadowsocks-netty- Netty实现shadowsocks客户端
- netty实现tcp客户端
- netty 实现 服务器 客户端通信
- netty客户端同步请求实现
- netty客户端同步请求实现
- 使用WCF来实现一个ShadowSocks客户端(序)
- 使用WCF来实现一个ShadowSocks客户端(一)
- 使用WCF来实现一个ShadowSocks客户端(二)
- 使用WCF来实现一个ShadowSocks客户端(三)
- 使用WCF来实现一个ShadowSocks客户端(四)
- Netty实现客户端和服务端的通信
- linux shadowsocks客户端配置
- shadowsocks客户端安装
- centos7 安装shadowsocks客户端
- ubuntu配置 shadowsocks客户端
- ubuntu安装shadowsocks客户端
- CentOS安装shadowsocks客户端
- Shadowsocks服务器端和客户端配置
- VM虚拟机下安装Centos7.0图文教程
- 【Java并发编程】之四:守护线程与线程阻塞的四种情况
- iOS10相册相机闪退bug
- webApi——通过文件流下载文件的实例
- Maven项目配置Tomcat下的JNDI数据源
- Netty实现shadowsocks客户端
- javasript常用正则表达式
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- ESD 保护芯片 PRTR5V0U2X
- java性能优化笔记(三)java程序优化
- ckeditor4.5.11+ckfinder_java2.6.2配置
- Linux系统上针对rm命令做审计
- JS、JQ中remove()、empty
- 【Java并发编程】之六:Runnable和Thread实现多线程的区别(含代码)