代理服务扩展

之前自己实现了一个代理服务,当时考虑的是只要支持SOCKS5就好了,因为我经常用CHROME,配合着SwitchySharp,体验还是很棒的。但是我现在有点讨厌CHROME,它现在太庞大了,占用资源太多了。而且我有锁定网页的习惯,一打开CHROME,就十几个甚至二十几个进程起来,让我很不爽。但是不得不说CHROME的安全设计还是非常棒的。然后我就试了下FireFox,额,我觉着它和IE差不多.然后就放弃了,然后看看了手头上的IE已经到11了,平时用起来感觉还是很不错的,所以我想支持IE的代理。

IE的代理机制比较囧,比如说它只支持SOCKS4,不支持SOCKS5,然后又分为HTTP代理,HTTPS代理,还有FTP代理。也没有像CHROME提供强大的代理插件机制。虽然IE提供了PAC机制,但是不得不说,这个机制也很鸡肋,没有像SwitchySharp那样可以做到实时的增减规则。针对以上原因,我就在原有的代码基础上增加了上面的几种代理,不过没支持FTP代理。

SOCKS4代理

SOCKS4协议比较简单,可以参考的文档是WIKI的这篇,还有OpenSSH的这篇。后面还有个SOCKS4A协议,不过这个SOCKS4A基本上没见到人用过。SOCKS4协议的CONNECT命令格式很简单,就一个请求包和回应包。请求包的第一个字段是版本号,占用1个字节,就是0x04,第二个字段是命令类型,占用1个字节,0x01表示CONNECT命令,即请求链接哪个IP : PORT,0x02是BIND,一般用于FTP场景,我没有实现。第三个字段是对端端口,占用2个字节,字节序是网络字节顺序;第四个字段是对端IP,占用4个字节,字节序是网络字节顺序;第五个字段是USERID,可变长度,以0x00结尾。这里要注意的是,在IE11下,USERID为当前用户名,不会为空。所以要读取完整的USERID和最后的0x00。

回应包第一个字段占用一个字节,数据为0;第二个字段占用一个字节,表示状态,0X5A表示成功,0X5B表示拒绝或者失败等等;第三个字节和第四个字段一共6个字节,会被忽略,直接填0即可。

整个协议简单很多,比SOCKS5简单多,但是没有SOCKS5强大。因为SOCKS4只支持IP : PORT方式,也就意味着IEFQ的时候,会自己先走本地DNS,然后拿到地址后才去走SOCKS代理。这里带来的问题是,如果DNS被污染了,就意味着FQ失败了。所以还得用后面的HTTP代理和HTTP隧道。

HTTP Tunnel (HTTP隧道)

HTTP隧道比较简单。就是客户端通过HTTP协议链接到服务端,请求服务端去链接某个域名或者IP的某个端口。协议非常简单,即客户端发送CONNECT Domain : Port HTTP/1.0\r\n\r\n。服务端收到该请求后会去链接指定的域名和端口,链接成功后,会回复客户端HTTP/1.0 200 Connection established\r\n\r\n 客户端收到该回复后,就开始把数据通过代理转发过去。这个时候的代理是盲转,和SOCKS协议一样。

用GO实现的时候也相对来说比较简单,通过net/http包即可完成。自己实现一个ServeHTTP方法,然后发现是CONNECT方法的请求就把连接Hijacked掉。具体代码如下:

hj, ok := response.(http.Hijacker)
if !ok {
    http.Error(response, "Hijacker failed", http.StatusInternalServerError)
    return
}
conn, _, err := hj.Hijack()
if err != nil {
    http.Error(response, err.Error(), http.StatusInternalServerError)
    return
}
defer conn.Close()

要注意的一点是Hijack后,如果要回复HTTP协议格式的数据,就要自己去操作了,没有办法再使用net/http.ResponseWriter提供的方法了。好在GO的fmt包提供了Fprint/Fprintf函数,所以操作起来也还算简单。

另外一点是,这个HTTP隧道允许在CONNECT发起时,在BODY里携带额外数据以达到优化的目的。所以还要在建立远端链接后,检查是否还有BODY数据,如果有的话,就把数据发出去。

HTTP Proxy

我原先认为的是既然有了HTTP隧道方式的代理机制了,那就都用这套呗,结果IE不这样,HTTP隧道只用在了HTTPS类型的URL,而普通的HTTP URL则走的是普通的HTTP代理机制。HTTP普通的请求类似于下面这样:GET /xxx/yyyy/zzzz.html HTTP/1.0,而HTTP代理则是GET http://www.qqqq.com/xxx/yyy/zzz.html HTTP/1.0,然后还会增加一个额外的HTTP首部Proxy-Connection。这个首部用来干嘛的自行GOOGLE。处理客户端发来的HTTP代理请求时,我的做法是把URL替换为正常的相对URI,然后检查是否存在Proxy-Connection,如果存在,则获取对应的值,并删除该首部,并添加Connection首部,其值为原Proxy-Connection对应的值。然后转发到对端服务器。 GO提供了一个包net/http/httputil,其中封装了一个反向代理的实现。只需要提供建立链接的函数以及对http.Request处理的函数即可。代码具体如下:

func NewHTTPProxy(remoteSocks, cryptoMethod string, password []byte) *HTTPProxy {
    return &HTTPProxy{
        ReverseProxy: &httputil.ReverseProxy{
            Director: director,
            Transport: &http.Transport{
                Dial: func(network, addr string) (net.Conn, error) {
                    return dial(network, addr, remoteSocks, cryptoMethod, password)
                },
            },
        },
    }
}

func dial(network, addr, remoteSocks, cryptoMethod string, password []byte) (net.Conn, error) {
    tcpAddr, err := net.ResolveTCPAddr(network, addr)
    if err != nil {
        return nil, err
    }
    remoteSvr, err := NewRemoteSocks(remoteSocks, cryptoMethod, password)
    if err != nil {
        return nil, err
    }

    // version(1) + cmd(1) + reserved(1) + addrType(1) + domainLength(1) + maxDomainLength(256) + port(2)
    req := []byte{0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
    copy(req[4:8], []byte(tcpAddr.IP.To4()))
    binary.BigEndian.PutUint16(req[8:10], uint16(tcpAddr.Port))
    err = remoteSvr.Handshake(req)
    if err != nil {
        remoteSvr.Close()
        return nil, err
    }
    conn := &HTTPProxyConn{
        RemoteSocks: remoteSvr,
    }
    return conn, nil
}

func director(request *http.Request) {
    u, err := url.Parse(request.RequestURI)
    if err != nil {
        return
    }
    request.RequestURI = u.RequestURI()
    v := request.Header.Get("Proxy-Connection")
    if v != "" {
        request.Header.Del("Proxy-Connection")
        request.Header.Del("Connection")
        request.Header.Add("Connection", v)
    }
}

总结:

本质上HTTP代理和HTTP隧道可以通过同一个端口实现的,但是我没有这样去做,因为我觉着代码分开更方便测试和修改。可以省去很多的麻烦。不过通过简单的组合也一样可以复用同一个端口,我后面会试着去修改。HTTP PROXY和TUNNEL现在在同一个端口实现,通过简单的组合就实现了对应的功能。而SOCKS4和SOCKS5按理来说也是可以用同一个端口的,但是考虑到代码中要判断版本之类的问题,我觉着这样还不如直接分开实现来的简单。

后面还可以考虑的是把SwitchySharp的代理策略移植到该代理服务上,然后再写个IE插件用来实现类似SwitchySharp的功能,这样的话,会方便很多。顺便说下,其实现在IE做的很不错。

时间: 2024-08-07 16:33:05

代理服务扩展的相关文章

一起来做chrome扩展《可配置的代理》

一.本文主要涉及相关内容: chrome.proxy pacScript browser_action popup localStroage 二.预览 (代理运行截图,图中的代理服务器有防火墙,暂不对外) 如图所示,代理配置界面通过点击browser_action按钮打开,在popup页面内进行.可以设置服务器,也可以添加删除站点.好了,开始一些简单的说明工作 三.proxy 在chrome扩展中,要使用proxy同任何一种chrome对象一样,需要在json配置中允许chrome操作,在之前的

基于SNMP的MIB扩展方法研究

文章较长,此文章背景:毕业设计,直接去搜索我的毕业设计选题,当时还傻傻的用百度,在百度文库和CSDN等下载了很多论文.搜索到的论文有以下几种: 1)完全胡扯,听说MIB“树状”模型,直接就有写ta用二叉树实现了,恩,我上次用了Java和氧化还原反应成功拿到抓到外星人,你信不信? 2)介绍了很多知识背景,然后一下子ta们就实现了,他们做了什么工作很少涉及. 3)有位盆友一直写自己在做这件事情,ta也说网上有很多方法,但是没有有价值的方法,ta决定实现一个,但是,ta再也没有更新ta的博客.o(╯□

Nginx反向代理(基于目录动静分离、不同浏览器类型不同代理、基于扩展名的不同代理)

proxy_pass http_proxy_module proxy_pass指令输入ngx_http_proxy_module模块,此模块可以将请求转发到另外一台服务器 官网:http://nginx.org/en/docs/http/ngx_http_proxy_module.html =============================================== location /some/path/ { proxy_pass http://127.0.0.1; } ==

iOS8扩展插件开发配置

一.iOS8扩展插件概述 WWDC14除了发布了OS X v10.10和switf外,iOS8.0也开始变得更加开放了.说到开放,当然要数应用扩展(App Extension)了.顾名思义,应用扩展允许开发者扩展应用的自定义功能和内容,能够让用户在使用其他应用程序时使用该项功能,从而实现各个应用程序间的功能和资源共享.可以将扩展理解为一个轻量级(nimble and lightweight)的分身. 以下为常见的三类插件: Target Type Extension point identifi

扩展欧几里得算法的模板实现

我居然现在还记不住扩欧的板子,我太弱啦! 扩展欧几里得算法解决的是这样的问题: 给定一个不定方程组ax+by=gcd(a,b),求他的一组整数解 先给出实现代码 void exgcd(int a,int b,int &x,int &y) { if(!b) { x=1,y=0;//gcd(a,0)显然等于1*a-0*0=a return a; } int ans=exgcd(b,a%b,x,y); int tem=x; x=y; y-=tem-(a/b)*y; return ans;} 但实

c# 扩展方法奇思妙用基础篇五:Dictionary<TKey, TValue> 扩展

Dictionary<TKey, TValue>类是常用的一个基础类,但用起来有时确不是很方便.本文逐一讨论,并使用扩展方法解决. 向字典中添加键和值 添加键和值使用 Add 方法,但很多时候,我们是不敢轻易添加的,因为 Dictionary<TKey, TValue>不允许重复,尝试添加重复的键时 Add 方法引发 ArgumentException. 大多时候,我们都会写成以下的样子: var dict = new Dictionary<int, string>()

C#3.0 扩展方法

扩展方法 在很多时候我们需要编写各种各样的帮助类,因为官方提供的再全面,也会有未包含到的地方,这时一个静态的帮助类就可以帮我们解决问题 举一个不是很恰当的例子,假如要对一个字符串进行验证其内容不为null并且等于admin但是很多地方都要调用,按照封装思想要封装成一个方法,看起来可能是下面这样子的 这并没有任何问题,代码也是比较简单,但是很难会有一种亲近感,因为StringiHelper.ValidArg这样的调用存在.比如这个验证应该是官方应该考虑到的,但是他们忽略了,编写这样一个扩展后会有很

php安装swoole扩展

编译安装 Swoole扩展是按照php标准扩展构建的.使用phpize来生成php编译配置,./configure来做编译配置检测,make进行编译,make install进行安装. 请下载releases版本的swoole,直接从github主干上拉取最新代码可能会编译不过 如果当前用户不是root,可能没有php目录的写权限,安装时需要sudo或者su 如果是在git分支上直接git pull更新代码,重新编译前务必要执行make clean 安装准备 安装swoole前必须保证系统已经安

Linux下利用phpize安装memcashe的php源码扩展包

phpize是php的一种构建工具,为PHP扩展准备构建环境,通过phpize可以编译php的扩展源码文件为php扩展模块. 一.安装 phpize工具可以通过安装php-dev包自动集成安装.安装完成后php的bin目录下会有phpize这个命令. 二.使用 举例:在原来编译好的php中加入memcache扩展模块,使用phpize构建的方式如下. tar zxvf memcache-2.2.5.tgz cd memcache-2.2.5/ /usr/local/php/bin/phpize