使用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

时间: 2025-01-17 22:13:27

使用netty构建一个socks proxy的相关文章

我有一个socks proxy了,怎样基于他弄一个http proxy?

\win32-dg9_9_13\dg9_9_13\DGROOT\bin 配置文件 [email protected] # mail-address of the administrator of this DeleGate SERVER=http # the protocol DeleGate speaks with its clients -P8080 # the entrance port on which DeleGate waits clients SOCKS=localhost:108

Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇

目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于Erlang的RabbitMQ.基于Java的ActiveMQ/Apache Kafka.基于C/C++的ZeroMQ等等,都能进行大批量的消息路由转发.它们的共同特点是,都有一个消息中转路由节点,按照消息队列里面的专业术语,这个角色应该是broker.整个消息系统通过这个broker节点,进行从消息生产者Producer到消费者Consumer的消息路由.当然了,生产者和消费者可以是多对多的关系.消息路由的时候,可以

第一节 构建一个简单的WCF应用

先吐个槽,到目前为止接触的东西也就是些ado.net.select.delete.update.create.临时表的批量操作.及稍微复杂点的几个表之间查询再带几个excel导入导出 然后会点前端的js.jquery等,所以在公司目前薪水并不高(能在广州生活下去吧,什么买车买房的想都别想),拿自己身边的同志一比较感觉心里不怎么平衡,凡事还是得靠自己 自强才是硬道理,就吐到这里吧!开始我的wcf之旅吧 本人理工科类型的文笔很烂 希望各位大神不要喷小弟哦(参照的书本:WCF全面解析) 咱们还是从小学

Netty构建分布式消息队列实现原理浅析

在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子,具体演示了AvatarMQ所具备的基本消息路由功能.而本文的写作目的,是想从开发.设计的角度,简单的对如何使用Netty,构建分布式消息队列背后的技术细节.原理,进行一下简单的分析和说明. 首先,在一个企业级的架构应用中,究竟何时需引入消息队列呢?本人认为,最经常的情况,无非这几种:做业务解耦.事件

Apache thrift - 使用,内部实现及构建一个可扩展的RPC框架

本文首先介绍了什么是Apache Thrift,接着介绍了Thrift的安装部署及如何利用Thrift来实现一个简单的RPC应用,并简单的探究了一下Thrift的内部实现原理,最后给出一个基于Thrift的可扩展的分布式RPC调用框架,在中小型项目中是一个常见的SOA实践. Thrift介绍 Apache Thrift是Facebook 开发的远程服务调用框架,它采用接口描述语言(IDL)定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java,

Netty:一个非阻塞的客户端/服务器框架

Netty:一个非阻塞的客户端/服务器框架 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs Netty是一个异步事件驱动的网络应用框架,为Java网络应用的开发带来了一些新活力.Netty由协议服务器和客户端所组成,可用于快速开发可维护的高性能软件.Netty应用框架及其工具简化了网络编程,而且由Netty社区进行维护. Netty还被归类为NIO客户端/服务器框架,用它能够快速.简易地开发网络应用,使得TCP和UDP套接字服务器的网络编程得以简化和

Netty构建Http服务器

Netty 是一个基于 JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速地开发网络应用程序,比如客户端和服务端的协议.Netty大大简化了网络程序的开发过程比如TCP和UDP的 Socket的开发.Netty 已逐渐成为 Java NIO 编程的首选框架,Netty中,通讯的双方建立连接后,会把数据按照ByteBuf的方式进行传输,因此我们在构建Http服务器的时候就是通过Htt

如何构建一个有效的服务治理平台

本文我们重点讨论如何构建一个有效的服务治理平台,话不多说,直接切入整体.构建服务治理平台基于“管理”,“度量”,“管控”三个层面统筹考虑安排.具体来讲,又可以分为六个层次来考虑问,分别是:服务管理流程体系,服务治理平台,服务治理核心架构,服务协议规范,服务支撑工具,服务运行环境.六个层面的具体关系如下图所示: 接下来我们分别来看一下每个层面的具体内容. 01 服务治理框架 当下无论对于什么样类型的服务治理核心框架,无论是开源还是自建,在功能层面相差不大,但技术实现却有所差别.但就落地实践而言,自

Android学习笔记-构建一个可复用的自定义BaseAdapter

转载自http://www.runoob.com/w3cnote/android-tutorial-customer-baseadapter.html   作者:coder-pig 本节引言: 如题,本节给大家带来的是构建一个可复用的自定义BaseAdapter,我们每每涉及到ListView GridView等其他的Adapter控件,都需要自己另外写一个BaseAdapter类,这样显得非常麻烦, 又比如,我们想在一个界面显示两个ListView的话,我们也是需要些两个BaseAdapter