使用netty构建一个socks proxy
使用netty构建一个socks proxy
最近在做的项目,需要自己搭建一个socks代理。netty4.0附带了一个socks代理的样例,但是3.x就没有这个东西了,碰巧使用的又是3.7,就只能自己摸索并实现一遍,也算是对netty和socks协议的一个熟悉。socks代理涉及到协议解析、server、client等功能,是一个比较复杂的网络程序,对于学习netty的使用也是非常好的例子。
socks是在传输层之上的一层协议,主要功能是提供代理认证等功能。socks协议虽然是应用层协议(在TCP/IP4层协议栈里),本身可以理解为一个信道,可以传输任何TCP/UDP内容。例如著名的科学上网软件就是基于socks协议,对通信内容进行加密实现的。
TCP/IP协议栈的结构中,下层协议总会在上层协议内容之前加上自己的头。而socks协议稍微不同,其实它对比TCP协议,仅仅是多了验证部分,验证之后,完全是使用TCP来进行传输,而没有socks报文头。socks协议的具体内容可以参考rfc1928。这一点来说,其实将socks理解成与其他应用层协议平级也没什么问题。
一个最基本的socks连接流程是这样的:
那么我们开始netty之旅吧。
首先我们需要建立一个server:
public void run() {
// 新建线程池
Executor executor = Executors.newCachedThreadPool();
Executor executorWorker = Executors.newCachedThreadPool();
ServerBootstrap sb = new ServerBootstrap(
new NioServerSocketChannelFactory(executor, executorWorker));
// 初始化代理部分使用的client
ClientSocketChannelFactory cf =
new NioClientSocketChannelFactory(executor, executorWorker);
//设置处理逻辑
sb.setPipelineFactory(
new SocksProxyPipelineFactory(cf));
// Start up the server.
sb.bind(new InetSocketAddress(1080));
}
如你所见,主要的处理逻辑以SocksProxyPipelineFactory的形式提供。SocksProxyPipelineFactory的代码包括几部分:
public class SocksProxyPipelineFactory implements ChannelPipelineFactory {
private final 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;
}
}
这里要详细解释一下几个handler的作用:
ChannelUpstreamHandler
用于接收之后的处理,而ChannelDownstreamHandler
则相反,用于写入数据之后的处理。这两个都可以附加到ChannelPipeline
中。偷个懒,直接附上netty的ChannelPipeline中的一段很有爱的javadoc:
I/O Request
via {@link Channel} or
{@link ChannelHandlerContext}
|
+----------------------------------------+---------------+
| 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
用于对socks的请求进行解码。你可能会说,为什么没有SocksCmdRequest的解码?别急,netty的handler是可以动态添加的,这里我们先解码一个初始化的请求。SocksInitRequestDecoder是一个ChannelUpstreamHandler
,即接收流的处理器。
SocksMessageEncoder
是一个ChannelDownstreamHandler
,即输出时的编码器,有了它,我们可以很开心的在channel.write()里直接传入一个对象,而无需自己去写buffer了。
SocksServerHandler
是处理的重头。这里会根据请求的不同类型,做不同的处理。
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
SocksRequest socksRequest = (SocksRequest) e.getMessage();
switch (socksRequest.getSocksRequestType()) {
case INIT:
//添加cmd解码器
ctx.getPipeline().addFirst(SocksCmdRequestDecoder.getName(), new SocksCmdRequestDecoder());
//简单起见,无需认证
ctx.getChannel().write(new SocksInitResponse(SocksMessage.AuthScheme.NO_AUTH));
break;
case AUTH:
ctx.getPipeline().addFirst(SocksCmdRequestDecoder.getName(), new SocksCmdRequestDecoder());
//直接成功
ctx.getChannel().write(new SocksAuthResponse(SocksMessage.AuthStatus.SUCCESS));
break;
case CMD:
SocksCmdRequest req = (SocksCmdRequest) socksRequest;
if (req.getCmdType() == SocksMessage.CmdType.CONNECT) {
//添加处理连接的handler
ctx.getPipeline().addLast(SocksServerConnectHandler.getName(), new SocksServerConnectHandler(cf));
ctx.getPipeline().remove(this);
} else {
ctx.getChannel().close();
}
break;
case UNKNOWN:
break;
}
super.messageReceived(ctx, e);
}
前面两种INIT和AUTH就不做赘述了,后面当CMD为Connect时,添加一个处理连接的SocksServerConnectHandler
,它会起到client与外部server的桥梁作用。
这里我们先实现一个纯转发的handler-OutboundHandler
:
private class OutboundHandler extends SimpleChannelUpstreamHandler {
private final Channel inboundChannel;
OutboundHandler(Channel inboundChannel) {
this.inboundChannel = inboundChannel;
}
@Override
public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e) throws Exception {
final ChannelBuffer msg = (ChannelBuffer) e.getMessage();
synchronized (trafficLock) {
inboundChannel.write(msg);
}
}
}
它会把收到的内容,写入到inboundChannel
中,其他转发的作用。最后就是我们的SocksServerConnectHandler
了:
public class SocksServerConnectHandler extends SimpleChannelUpstreamHandler {
private final ClientSocketChannelFactory cf;
private volatile Channel outboundChannel;
final Object trafficLock = new Object();
public SocksServerConnectHandler(ClientSocketChannelFactory cf) {
this.cf = cf;
}
@Override
public void messageReceived(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() {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
// 外部server数据转发到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(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// client数据转发到外部server
inboundChannel.getPipeline().addLast("inboundChannel", 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();
}
}
});
}
}
好了,完工!输入curl --socks5 127.0.0.1:1080 http://www.oschina.net/
测试一下吧?但是测试时发现,怎么老是无法接收到响应?
使用wiredshark抓包之后,发现对外请求完全正常,但是对客户端的响应,则完全没有http响应部分?
一步步debug下去,才发现SocksMessageEncoder
出了问题!
@Override
protected 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;
}
这里只有SocksMessage才会被处理,其他的message全部被丢掉了!于是我们加上一行:
@Override
protected 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) {
//直接转发是ChannelBuffer类型
buffer = (ChannelBuffer) msg;
}
return buffer;
}
至此,一个代理完成!点这里查看代码:https://github.com/code4craft/netty-learning/tree/master/learning-src/socksproxy
使用netty构建一个socks proxy的更多相关文章
- 如何用Netty实现一个轻量级的HTTP代理服务器
为什么会想通过Netty构建一个HTTP代理服务器?这也是笔者发表这篇文章的目的所在. 其主要还是源于解决在日常开发测试过程中,一直困扰测试同学很久的一个问题,现在我就来具体阐述一下这个问题. 在日常 ...
- netty系列之:从零到壹,搭建一个SOCKS代理服务器
目录 简介 使用SSH搭建SOCKS服务器 使用netty搭建SOCKS服务器 encoder和decoder 建立连接 ConnectHandler 总结 简介 上一篇文章,我们讲到了netty对S ...
- Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇
目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于Erlang的RabbitMQ.基于Java的ActiveMQ/Apache Kafka.基于C/C++的ZeroMQ等等 ...
- Netty构建分布式消息队列实现原理浅析
在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...
- 利用Netty构建自定义协议的通信
在复杂的网络世界中,各种应用之间通信需要依赖各种各样的协议,比如:HTTP,Telnet,FTP,SMTP等等. 在开发过程中,有时候我们需要构建一些适应自己业务的应用层协议,Netty作为一个非常优 ...
- 自己用 Netty 实现一个简单的 RPC
目录: 需求 设计 实现 创建 maven 项目,导入 Netty 4.1.16. 项目目录结构 设计接口 提供者相关实现 消费者相关实现 测试结果 总结 源码地址:github 地址 前言 众所周知 ...
- 利用 vue-cli 构建一个 Vue 项目
一.项目初始构建 现在如果要构建一个 Vue 的项目,最方便的方式,莫过于使用官方的 vue-cli . 首先,咱们先来全局安装 vue-cli ,打开命令行工具,输入以下命令: $ npm inst ...
- 用Java构建一个简单的WebSocket聊天项目之新增HTTP接口调度
采用框架 我们整个Demo基本不需要大家花费太多时间,就可以实现以下的功能. 用户token登录校验 自我聊天 点对点聊天 群聊 获取在线用户数与用户标签列表 发送系统通知 首先,我们需要介绍一下我们 ...
- Netty构建Http服务器
Netty 是一个基于 JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速地开发网络 ...
随机推荐
- C#时间日期操作
一.C# 日期格式 DateTime dt = DateTime.Now; dt.ToString();//2005-11-5 13:21:25 dt.ToFileTime().ToString() ...
- 回调函数 callback 的简单理解
回调函数指当我执行完某一段代码之后在回过头来调用 jquery 最简单的例子 $(".className").each(function(i){alert(i)}) 她这个形参i是 ...
- mini-httpd源码分析-match.h
//字符串匹配,匹配返回 1,否则返回 0. //pattern可以通过任意个 | 字符,组合match_one中pattern的功能 int match(const char* pattern, c ...
- HTML5学习笔记之Input类型
Input类型——email email类型用于包含email地址的输入域,在输入地址时会自动验证email域的值 例:Email:<input type="email" n ...
- Qt 设置对话框背景(使用调色板,设置它的画刷,画刷可以是图片)
http://blog.csdn.net/ei__nino/article/details/7305234
- 如何自定义Intent.createChooser的显示结果
Intent是android核心的概念之一,Intent为android系统提供了真正的开放.android的姿态是开放了,但却没有做到位. 拿“发邮件”这一功能来说,为了使用Intent机制来发送邮 ...
- centos 6.7 perl 版本 This is perl 5, version 22 安装DBI DBD
<pre name="code" class="cpp">centos 6.7 perl 版本 This is perl 5, version 22 ...
- 「深入理解计算系统」从Hello World开始
从 hello world 开始 Table of Contents 1 程序源文件 2 程序源文件是什么 3 程序被编译 4 程序运行 4.1 读取命令 4.2 读取指令内容 4.3 执行过程 5 ...
- C++_auto
自动变量,自动获取类型,输出,泛型 自动变量,可以实现自动循环一维数组 自动循环的时候,对应的必须是常量 //auto自动变量,自动匹配类型 #include <iostream> usi ...
- 布线问题(prime)
布线问题 时间限制:1000 ms | 内存限制:65535 KB 难度:4 描述 南阳理工学院要进行用电线路改造,现在校长要求设计师设计出一种布线方式,该布线方式需要满足以下条件:1.把所有 ...