标签归档:代理

Sockets代理 之 Shadowsocks

ShadowSocks – http://shadowsocks.org

实现一个Socks代理,很多语言都提供有内置支持或扩展包可用,需要编写的代码也不会太多。ShadowSocks这个工具提供了多种语言的实现,从http://shadowsocks.org/en/download/servers.html可以看到,有Python、Go、C with libev、C++ with Qt, 这些实现版本中,都应该包含服务端和客户端工具,不过也有一些专门的客户端(主要提供图形操作界面),比如
Windows客户端(https://github.com/shadowsocks/shadowsocks-windows/releases)
Linux下的客户端(https://github.com/shadowsocks/shadowsocks-qt5/releases)
Mac OS X下的客户端(https://github.com/shadowsocks/ShadowsocksX-NG/releases),Android下的客户端(https://github.com/shadowsocks/shadowsocks-android,在Android下可打开https://play.google.com/store/apps/details?id=com.github.shadowsocks进行安装)。这个代理软件与一般的Socks5代理稍不一样的是:它不是一个纯粹的代理,它加入了加密的功能。 这个工具开启的代理功能和SSH开启的代理有本质不一样,SSH开启的代理会在本地和服务端之间建立一条持久的TCP链接,而shadowsocks只是在服务器开启了监听端口,客户端的链接随到随链。所以,对于需要HOLD大量链接的情况,C with libev是一个很好的选择。

注:
由于shadowsock不是一个纯的Sockets代理,所以必须搭配客户端使用。

服务端,这里关注C实现的版本:https://github.com/shadowsocks/shadowsocks-libev,它提供了比较完整的功能。客户端关注Windows下的版本,它是.NET软件,可能需要升级你的.NET版本。

在CentOS 7.x上,可以使用了一个yum源来安装(参考https://copr.fedorainfracloud.org/coprs/librehat/shadowsocks/):

#https://copr.fedorainfracloud.org/coprs/librehat/shadowsocks/repo/epel-7/librehat-shadowsocks-epel-7.repo
#vi /etc/yum.repos.d/librehat-shadowsocks.repo

[librehat-shadowsocks]
name=Copr repo for shadowsocks owned by librehat
baseurl=https://copr-be.cloud.fedoraproject.org/results/librehat/shadowsocks/epel-7-$basearch/
type=rpm-md
skip_if_unavailable=True
gpgcheck=1
gpgkey=https://copr-be.cloud.fedoraproject.org/results/librehat/shadowsocks/pubkey.gpg
repo_gpgcheck=0
enabled=1
enabled_metadata=1

安装:

yum install shadowsocks

rpm -qa | grep shadowsocks
shadowsocks-libev-2.5.5-1.el7.centos.x86_64

rpm -ql shadowsocks-libev-2.5.5-1.el7.centos.x86_64
/etc/default/shadowsocks-libev
/etc/shadowsocks-libev/config.json
/usr/bin/ss-local
/usr/bin/ss-manager
/usr/bin/ss-nat
/usr/bin/ss-redir
/usr/bin/ss-server
/usr/bin/ss-tunnel
/usr/lib/systemd/system/shadowsocks-libev.service
/usr/lib64/libshadowsocks-libev.so

编译安装(新版本可能有问题)

# Ubuntu
apt-get install --no-install-recommends build-essential automake autoconf libtool libssl-dev libpcre3-dev asciidoc xmlto
# CentOS
yum install gcc autoconf libtool automake make zlib-devel openssl-devel asciidoc xmlto

#克隆源码,或直接下载压缩包
git clone https://github.com/shadowsocks/shadowsocks-libev.git
cd shadowsocks-libev
./configure --prefix=/usr/local/ss
make
make install

#######################################
# CentOS下编译安装新版本参考:https://shadowsocks.be/4.html,以下是从脚本中提取的安装步骤
#安装必须的软件包
yum install epel-release
yum install -y unzip openssl openssl-devel gettext gcc autoconf libtool automake make asciidoc xmlto udns-devel libev-devel pcre pcre-devel git c-ares-devel

#下载安装
wget https://github.com/jedisct1/libsodium/releases/download/1.0.13/libsodium-1.0.13.tar.gz
tar zxf libsodium-1.0.13.tar.gz
cd libsodium-1.0.13
./configure —prefix=/usr && make && make install

wget http://dl.teddysun.com/files/mbedtls-2.6.0-gpl.tgz
tar xf mbedtls-2.6.0-gpl.tgz
cd mbedtls-2.6.0-gpl
make SHARED=1 CFLAGS=-fPIC
make DESTDIR=/usr install

wget https://c-ares.haxx.se/download/c-ares-1.12.0.tar.gz
tar zxvf c-ares-1.12.0.tar.gz
cd c-ares-1.12.0
./configure
make && make install

ldconfig

wget https://github.com/shadowsocks/shadowsocks-libev/releases/download/v3.1.0/shadowsocks-libev-3.1.0.tar.gz
tar zxf shadowsocks-libev-3.1.0.tar.gz
cd shadowsocks-libev-3.1.0
./configure --disable-documentation
make && make install

默认配置在/etc/default/shadowsocks-libev,配置文件/etc/shadowsocks-libev/config.json,服务启动脚本/usr/lib/systemd/system/shadowsocks-libev.service。默认实际使用到的就是/usr/bin/ss-server(如果要启动客户端,修改为ss-local即可),查看配置文件:

{
    "server":"120.xx.xx.28",
    "server_port":8388,
    "local_port":1080,
    "password":"xxxxxxxx",
    "timeout":60,
    "method":"aes-256-cfb"
}

不管是使用ss-local和ss-server,这几个参数都一样,注意:作为服务端,local_port应该是没有用的。客户端和服务端,都预置了密码和对称加密使用的算法,简单来说,它们通过密码认证,然后使用预置的算法产生一个秘钥,用这个秘钥来加解密传输的数据。不难看出,它跟传统的CA不同,秘钥的交换不是通过加密隧道来交换的。如果需要安全,不要使用这个工具。

这个包也包括了其它工具:

/usr/bin/ss-local	客户端,和服务端的配置一样
/usr/bin/ss-manager	用来管理服务端,比如在服务端开启多个端口(动态拉起ss-server)
/usr/bin/ss-nat		管理NAT映射
/usr/bin/ss-redir	客户端工具,用来实现透明代理
/usr/bin/ss-server	服务端
/usr/bin/ss-tunnel	用来实现本地端口转发,比如目的地址符合条件时,转发到本地某个端口

对于客户端,最典型的应用,就是在内网架设一个服务器,上面安装ShadowSocks-livev,然后启动客户端(也可以把参数写入配置,用c指定读取的配置文件):

ss-local -s 120.xx.xx.x -p 8388 -b 192.168.1.250 -l 1080 -k xxxxx -m aes-256-cfb

需要代理的机器,指定为192.168.1.250即可,这样就可以避免在每个终端上安装客户端。不过,如果需要连接多个代理服务器,就需要启动多个ss-local进程,并且指定不同的端口。

注:可以在服务器上设置多用户(实际是启动多个ss-server,用端口区分),客户端可以配置多个远程服务器,但是需要指定默认,需要更换时,需要切换一下默认远程服务器,从这里可知,一个客户端要对应哪个远程是随意的,目前看起来缺少的是在客户端管理远程服务器的工具(比如只启动一个客户端,动态或安装一定规则对应到不同远程服务器)

Window下的客户端(https://github.com/shadowsocks/shadowsocks-windows/releases):
ss

当然,这个客户端还提供了 系统代理模式(就是直接修改系统代理) 系统代理模式(全局与PAC) 切换服务器等功能。

Linux下的客户端不支持配置多个端口(不同端口对应不同的服务端),所以可以启动多个客户端来解决,比如分别对应建立配置文件,然后写一段简单粗暴的定时脚本,就可以Hold它:

#!/bin/bash

live=`ps -efH | grep '/etc/shadowsocks-libev/config_4441.json' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    /usr/bin/ss-local -a root -c /etc/shadowsocks-libev/config_4441.json -u 2>&1 > /dev/null &
fi

live=`ps -efH | grep '/etc/shadowsocks-libev/config_4442.json' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    /usr/bin/ss-local -a root -c /etc/shadowsocks-libev/config_4442.json -u 2>&1 >> /dev/null &
fi
 
live=`ps -efH | grep '/etc/shadowsocks-libev/config_4443.json' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    /usr/bin/ss-local -a root -c /etc/shadowsocks-libev/config_4443.json -u 2>&1 >> /dev/null &
fi

live=`ps -efH | grep '/etc/shadowsocks-libev/config_4444.json' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    /usr/bin/ss-local -a root -c /etc/shadowsocks-libev/config_4444.json -u 2>&1 >> /dev/null &
fi

exit 0

需要说明的是shadowsocks-qt5是一个跨平台的客户端(https://github.com/shadowsocks/shadowsocks-qt5/releases),以下链接是安装和使用说明:https://github.com/shadowsocks/shadowsocks-qt5/wiki:

Ubuntu通过PPA方式安装:

sudo add-apt-repository ppa:hzwhuang/ss-qt5
sudo apt-get update
sudo apt-get install shadowsocks-qt5

这个ppa对应的链接是:https://launchpad.net/~hzwhuang/+archive/ubuntu/ss-qt5
ss-ubuntu

而对应的Window版本长这个样子:
ss-window

最后:
作为服务端,如果使用的是Linux(CentOS或Ubuntu),最好的选择是shadowsocks-libev,而对于Windows,看起来只有一个选择(https://github.com/shadowsocks/libQtShadowsocks/releases,提供一个叫libQtShadowsocks的工具,既可做服务端,也可以做客户端); 作为客户端,在Windows和Ubuntu下https://github.com/shadowsocks/shadowsocks-qt5/releases这个较好。

这是一个百花齐放的开源产品。

HTTP代理 之 Squid与Node.js

实现HTTP代理有两种方式:
一种是普通代理,代理服务器充当的是一个中间的组件。这种方式简单来说就是接受来自客户端的HTTP报文(原本不应该发送给代理服务器的,强制发送给代理服务器),然后在服务器端做一些必要修改再转发出去(可能涉及via头,请求uri,来源ip等)。这种方式一般认为不能代理HTTPS流量。Nginx、Squid支持这种代理转发。

另一种是TCP隧道代理。这种代理可以以HTTP协议的方式来实现理论上任意TCP之上的应用层协议代理(可以代理HTTPS)。Squid支持这种方式,而Nginx不支持这种方式。

在Node.js中,实现这两种代理方式,只需数行代码。


普通代理(正向代理)

###Nginx HTTP正向代理示例
server {
    resolver 8.8.8.8;
    resolver_timeout 5s;
 
    listen x.x.x.x:8080;
 
    location / {
        proxy_pass $scheme://$host$request_uri;
        proxy_set_header Host $http_host;
    }
}

###Squid HTTP正向代理示例
acl localnet src 0.0.0.0/0

acl SSL_ports port 443
acl Safe_ports port 80

acl CONNECT method CONNECT

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports

http_access allow localnet
http_access allow localhost
http_access deny all

http_port 8080

#举例
# 1 原始HTTP报文
GET /action?b=123 HTTP/1.1
Host: blog.ifeeline.com
注:通过浏览器的代理后,GET /action?b=123 HTTP/1.1会被变成GET http://blog.ifeeline.com/action?b=123 HTTP/1.1

# 2 这个报文原样转发给x.x.x.x:8080,原本应该是直接发给blog.ifeeline.com的(Host字段)

# 3 根据HOST DNS到IP,把这个Http报文转发出去(如必要可做必要修改,比如添加请求头)

在Node.js中,这种方式的代理转发,实现非常简单:

var http = require('http');
var url = require('url');

function request(req, res) {
    var u = url.parse(req.url);
	
    //var options = {
	//  hostname : u.hostname, 
	//  port     : u.port || 80,
	//  path     : u.path,       
	//  method   : req.method,
	//  headers  : req.headers
    //};

    // 继续发给其它代理(比如Squid Nginx)  
    var options = {
        hostname : "120.xx.xx.192", 
        port     : 3333,
        path     : 'http://'+u.host+u.path,       
        method   : req.method,
        headers  : req.headers
    };

    var ireq = http.request(options, function(ires) {
        res.writeHead(ires.statusCode, ires.headers);
        ires.pipe(res);
    }).on('error', function(e) {
        res.end();
    });

    req.pipe(ireq);
}

http.createServer().on('request', request).listen(8080, '0.0.0.0');

另一种代理方式是隧道代理,目前应用非常的广泛,实际上原来非常的简单:HTTP客户端(浏览器)通过CONNECT方法请求代理服务器创建一条到达目的服务器和端口的TCP连接,然后把HTTP客户端(浏览器)传输过来的数据通过这个TCP连接直接转发。

很明显,相比之下,多了一次来回(CONNECT请求)。而后续的操作就是把数据从一个链接转到另一个链接,仅仅是转发TCP数据,实际就是把前面的输出作为后面的输入,如此而已。

Nginx不支持CONNECT请求,自然就无法支持隧道代理了。Squid支持隧道代理。在Node.js中用数行代码就可以实现隧道代理:

var http = require('http');
var net = require('net');
var url = require('url');

function connect(req, sock) {
    var u = url.parse('http://' + req.url);

    var insock = net.connect(u.port, u.hostname, function() {
        sock.write('HTTP/1.1 200 Connection Established\r\n\r\n');
	// 回传客户端
        insock.pipe(sock);
    }).on('error', function(e) {
        sock.end();
    });
    // 客户端传送数据
    sock.pipe(insock);
}

http.createServer().on('connect', connect).listen(8080, '0.0.0.0');

代理接收到connect方法请求后,建立一条到目标服务器的TCP链接,然后向客户端回送确认,客户端得到确认后继续使用sock把数据发送过来,代理服务器把从sock过来的数据推送到目标服务器。

一个基本事实:
浏览器设置HTTP代理时,如果是HTTP链接,则把HTTP报文直接发送到代理服务器。如果是HTTPS链接,则采用以上描述的隧道方式,先发送一个connet请求,然后发送数据。大体意思是能用普通代理的就不用隧道代理,毕竟隧道代理多了一个来回。(并不是HTTP链接不能走隧道代理)

把以上两段Node.js代码合并一起就可以实现全功能的HTTP代理:

var http = require('http');
var net = require('net');
var url = require('url');

function request(req, res) {
    var u = url.parse(req.url);
	var options = {
	    hostname : u.hostname, 
	    port     : u.port || 80,
	    path     : u.path,       
	    method   : req.method,
	    headers  : req.headers
	};
    };

    var ireq = http.request(options, function(ires) {
        res.writeHead(ires.statusCode, ires.headers);
        ires.pipe(res);
    }).on('error', function(e) {
        res.end();
    });

    req.pipe(ireq);
}

function connect(req, sock) {
    var u = url.parse('http://' + req.url);

    var insock = net.connect(u.port, u.hostname, function() {
        sock.write('HTTP/1.1 200 Connection Established\r\n\r\n');
	// 回传客户端
        insock.pipe(sock);
    }).on('error', function(e) {
        sock.end();
    });
    // 客户端传送数据
    sock.pipe(insock);
}

http.createServer().on('request', request).on('connect', connect).listen(8080, '0.0.0.0');

对于HTTPS链接的代理,从客户端到代理服务器,如果走的是HTTP,实际也不会增加安全问题,除了connect链接暴露的目标服务器和端口号外,后续的数据是加密的,中间人不可能解密。

如果确实担心这个问题,可以签发自签名的证书。 让客户端到代理之间也走HTTPS:

openssl genrsa -out root.pem 2048
openssl req -new -x509 -key root.pem -out root.crt -days 99999

注意:Common Name务必填写代理服务器IP。

把root.crt下载下来添加到系统受信任根证书列表中,完成这个步骤后从浏览器和代理,代理和目标服务器之间就可以建立安全链接了(虽然过程有伪造)。

对于以上Node.js的代码,只需要修改一点点:

var options = {
    key: fs.readFileSync('./root.pem'),
    cert: fs.readFileSync('./root.crt')
};

https.createServer(options).on();

添加根证书,http改为https而已。

—————————————————————-
以下是一个Squid代理配置实例:

via off
forwarded_for transparent
dns_nameservers 8.8.4.4 8.8.8.8

## IP白名单
acl localnet src 120.x.x.x
## IP白名单_公司内网
acl localnet src x.x.0.0/16
acl localnet src x.x.0.0/16
## IP白名单_本地网络
acl localnet src 10.0.0.0/8
acl localnet src 172.16.0.0/12
acl localnet src 192.168.0.0/16

## SSL端口
acl SSL_ports port 443

## 安全端口
acl Safe_ports port 80		# http
acl Safe_ports port 21		# ftp
acl Safe_ports port 443		# https
acl Safe_ports port 70		# gopher
acl Safe_ports port 210		# wais
acl Safe_ports port 1025-65535	# unregistered ports
acl Safe_ports port 280		# http-mgmt
acl Safe_ports port 488		# gss-http
acl Safe_ports port 591		# filemaker
acl Safe_ports port 777		# multiling http

## 启用HTTP隧道代理 
acl CONNECT method CONNECT

## 非安全端口
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports

## 运行IP白名单
http_access allow localnet
http_access allow localhost
http_access deny all

## 端口监听
http_port 4040

coredump_dir /var/spool/squid3

refresh_pattern ^ftp:		1440	20%	10080
refresh_pattern ^gopher:	1440	0%	1440
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0
refresh_pattern .		0	20%	4320

参数via off和forwarded_for transparent是建立透明代理的基础。目标机器就无法完全感知是被代理了。

由于内容直接使用HTTP转发,不管原始链接是HTTP还是HTTPS,目标IP与域都是暴露的,所以一旦被过滤,就会出现连接被重置的提示,所以,为了避免过滤,需要做加密。默认安装的Squid并不支持开启https端口,所以需要重新编译。可以使用Stunnel来建立加密隧道,然后由Stunnel解密后转发给Squid,这个做法很常见。

Sockets代理 之 SSH Sockets

使用SSH来开启Socks代理:

ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22
root@x.x.x.x's password:

这样就可以启动一个Socks,C表示压缩数据传输,N表示不进入Bash。(比如浏览器)设置Socket代理IP为127.0.0.1端口为7070,然后就可以通过x.x.x.x(x.x.x.x可以是本机地址)代理上网了。

SSH可以通过-i指定一个私钥来进行认证(这样就不需要输入密码),这种方式意味在需要在服务器端对应的用户里放置一个公钥:

root@xxx:~/.ssh ls
authorized_keys  known_hosts
root@xxx:~/.ssh# pwd
/root/.ssh
root@xxx:~/.ssh:~/.ssh# ls
authorized_keys  known_hosts
root@xxx:~/.ssh:~/.ssh# cat authorized_keys 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCiaMoJRe..........

公钥放进去后,在客户端就需要指定其对应的私钥:

ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 -i /roo/is_rsa

这样就不需要输入密码了。Linux中SSH不支持直接输入密码,但是可以使用一个包装工具来完成(sshpass):

yum install sshpass

#运行sshpass
sshpass -p "passwd" ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 

#查看进程可见:sshpass就是一个包装器
sshpass -p zzzzzzzzzz ssh -CN -D 127.0.0.1:7070 root@127.0.0.1 -p 22
ssh -CN -D 127.0.0.1:7070 root@127.0.0.1 -p 22

注:这里是监听本机的127.0.0.1的7070端口,本机通过SSH把数据传输到远程服务器,把远程服务器设置在本机也是可以的,这样本机和远程都一样的IP。

为了防止进程异常退出,可以定时运行一个SHELL脚本:

#1 使用sshpass包装器

#!/bin/bash

live=`ps -efH | grep 'sshpass' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    sshpass -p "passwd" ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 2>&1 >> /dev/null
fi


#2 不使用sshpass包装器时,需要在远程服务器上设置放置好公钥
#!/bin/bash

live=`ps -efH | grep 'ssh -CN -D' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 2>&1 >> /dev/null
fi

本地和远程之间传输数据实际是经过加密的(走SSH),就算本机和远程是同一条机器时也是如此。本地和客户端建立的TCP是一条持久链接:

netstat -ntp

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name   
tcp        0      0 127.0.0.1:22            127.0.0.1:39426         ESTABLISHED 20575/sshd: root                    
tcp        0      0 127.0.0.1:39426         127.0.0.1:22            ESTABLISHED 20574/ssh  

客户端ssh随机打开了一个端口(39436)和远程22端口进行链接。所有从7070过来的数据,都会转发到39436端口,由它把数据发送到服务端。不过这个本地转发是透明的。到此,我们有两个结论:
一、所有的数据都是通过一条持久链接发送到远程的
二、由SSH开放的端口(这里是7070),需要HOLD住所有的链接,等到服务端返回后,再转发回去。
所以,SSH开启的代理,不适用于高并发场景。对于高并发的场景,代理需要HOLD住大量链接,然后在和服务端链接时应该一一对应,或者开一个池。但是对于一般的代理上网,是够用的。

在Windows下也有对应的工具,下载地址:http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html。这里有一个叫Plink的工具,它类似sshpass的功能:

plink.exe -C -N -D 127.0.0.1:7070 root@x.x.x.x -pw password -P 22 -v

参数C表示启用压缩,N表示不进入终端,-pw来指定密码,-P指定端口(注意这里是大写的P), -v表示实时的输出日志。使用Plink这个工具(或者Putty)也可以通过-i来指定私钥。

监控脚本,在Windows下:

:1
bin\plink.exe -C -N -D 127.0.0.1:7070 root@x.x.x.x -pw password -P 22 -v
goto 1

保存为一个bat文件,需要代理时,双击它,然后弹出一个命令行窗口,由于加了-v参数,可以实时参考输出日志(证明它是工作的),不需要时直接关闭即可。

最后就是浏览器的代理配置了(以火狐为例子):
工具 – 选项
firefox-setting

firefox-agent

看起来,虽然实现了使用SSH Socks来上网,但是还是稍有麻烦:
1 不使用代理时,需要重新进入 工具 – 选项,找到网络,然后点击不使用代理
2 每次要上网代理时,需要运行一个命令行窗口

为了解决第一个麻烦问题,我们可以在firefox中安装一个叫autoproxy的插件(也有其它类似的),安装之后浏览器上会有一个按钮,点击切换是否使用代理:
autoproxy
可以配置多个代理,方便切换:
autoproxy-switch
Autoproxy这个插件看起来从2013年开始就再没有更新了。

至于第二个问题,我们需要找到一个客户端管理工具。目前在使用的工具:
1 http://nemesis2.qx.net/pages/MyEnTunnel (需要翻墙)
下载的是一个exe安装文件,一路安装下来:
myentunnel
其中lng是语言文件,ini是配置文件,可以发现plink.exe,所以这个工具是plink.exe包装器,让使用plink.exe使用更加方便一点而已。双击myentunnel.exe:
myentunnel-setting
这里的配置跟plink.exe的命令使用是对应的。可以设置多个配置,默认的是叫default的配置,可以点击右下角的红锁(或绿锁),添加配置,这个配置实际是对应命令目录下的ini文件。看来,配置无法删除,只能到文件夹里面删除对应的文件。

可以把这个文件夹拷贝下来。

2 另一个可用的工具是Bitvise Tunnelier,它不是plink.exe的包装器,可以互联网搜索看看。