前言:前段时间新出现了几个支持IKEv2的VPN,试用了一下,感受速度嗖嗖的,不过流量贵贵的。作为一个有尊严的技术人员,怎能甘心就这样把钱轻易送给别人呢。经过一番观察,发现该vpn直连的其实是国内的阿里云。那么原理就很明确了,客户端连阿里云,然后阿里云进行国内外分流。因为阿里云机房的网络肯定杠杠的,所以不管访问国内和国外都很快。明确原理之后,我做了好一番尝试,失败了好几次之后,终于于前天调试好了。

架构:客户端 <-> 阿里云 <-> 海外VPS

本文的重点是分流转发部分(也是我搜索许久,发现资料很少的部分),所以其它内容只是简略介绍。

零、基础知识

要跨越长城,其实得分两步:一是获取封锁网站的正确DNS解析,二是使用代理访问被封锁的IP。

就本文来说,获取正确DNS解析是通过dnsmasqss搭配来完成的。默认上游DNS是114.114.114.114,被封锁网站走ss-tunnel转发到8.8.8.8解析。

更多基础知识,请阅读FreeRouter,作者从网络基础到长城运行原理及如何跨越都讲到了。

一、海外服务器搭建ss

这一步网上教程一大片,就不重复了。我用的是shadowsocks-libev。如果这一步都无法独立完成,那后边的就不需要看了。

二、国内VPS搭建strongswan

centos 直接yum install strongswan就好。其它系统请自行搜索相应安装方式。

配置strongswan。我懒得弄证书了,所以认证方式为预共享密钥(因为我只需要iphone和mac上用,所以只配了这俩,其他客户端请自行搜索)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
vi /etc/strongswan/ipsec.conf (建议先配置一下默认配置)

config setup
    uniqueids=no              #允许多个客户端使用同一个证书,多设备同时在线
    strictcrlpolicy=no

#所有项目共用的配置项
conn %default
    keyexchange=ike              #ikev1 或 ikev2 都用这个
    left=%any                    #服务器端标识,%any表示任意
    leftsubnet=0.0.0.0/0         #服务器端虚拟ip, 0.0.0.0/0表示通配.
    right=%any                   #客户端标识,%any表示任意
    ikelifetime=60m
    keylife=20m
    rightsourceip=172.0.0.0/24    #因为阿里云默认有两个网卡,内网网卡用了10网段。所以我这里设置的是172.0.0.0/24

#供 os x 使用, 使用 PSK 预设密钥
conn IPSec-IKEv1-PSK
    keyexchange=ikev1
    fragmentation=yes
    leftauth=psk
    rightauth=psk
    rightauth2=xauth
    auto=add

# ios IKEv2-PSK
conn iOS_IKEv2-PSK
    auto=add
    dadaction=clear
    keyexchange=ikev2
    ike=aes256-sha1-modp1024
    leftauth=psk
    leftid=v.sosonemo.me
    rightauth=eap-mschapv2

以上配置文件仅限于对我来说可用,具体各条目啥含义我也不是完全明白。

配置密码文件:

1
2
3
4
vi /etc/strongswan/ipsec.secrets

: PSK "xxx"   #预共享密钥
user : EAP "xxx"  # 用户与密码

配置strongswan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
vi /etc/strongswan/strongswan.conf

charon {
        load_modular = yes
        plugins {
                include strongswan.d/charon/*.conf
        }
        duplicheck.enable = no #是为了你能同时连接多个设备,所以要把冗余检查关闭
        compress = yes
        dns1 = xxx.xxx.xxx.xxx  #后文用dnsmasq,这里要设置成阿里云的公网ip。不知道为啥不能设置成127.0.0.1 如果有知道请告知
}

三、客户端设置

iOS9现在虽然可以选择新建IKEv2的VPN了,但是无法使用预共享密钥,所以这里需要使用mobileconfig文件。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PayloadContent</key>
    <array>
        <dict>
            <key>IKEv2</key>
            <dict>
                <key>AuthName</key>
                <string>{username}</string>
                <key>AuthPassword</key>
                <string>{password}</string>
                <key>AuthenticationMethod</key>
                <string>SharedSecret</string>
                <key>ChildSecurityAssociationParameters</key>
                <dict>
                    <key>DiffieHellmanGroup</key>
                    <integer>2</integer>
                    <key>EncryptionAlgorithm</key>
                    <string>AES</string>
                    <key>IntegrityAlgorithm</key>
                    <string>SHA1-96</string>
                    <key>LifeTimeInMinutes</key>
                    <integer>1440</integer>
                </dict>
                <key>DeadPeerDetectionRate</key>
                <string>Medium</string>
                <key>DisableMOBIKE</key>
                <integer>0</integer>
                <key>DisableRedirect</key>
                <integer>0</integer>
                <key>EnableCertificateRevocationCheck</key>
                <integer>0</integer>
                <key>EnablePFS</key>
                <integer>0</integer>
                <key>ExtendedAuthEnabled</key>
                <true/>
                <key>IKESecurityAssociationParameters</key>
                <dict>
                    <key>DiffieHellmanGroup</key>
                    <integer>2</integer>
                    <key>EncryptionAlgorithm</key>
                    <string>AES</string>
                    <key>IntegrityAlgorithm</key>
                    <string>SHA1-96</string>
                    <key>LifeTimeInMinutes</key>
                    <integer>1440</integer>
                </dict>
                <key>LocalIdentifier</key>
                <string>{rightid}</string>
                <key>RemoteAddress</key>
                <string>{your_server_address}</string>
                <key>RemoteIdentifier</key>
                <string>{leftid}</string>
                <key>SharedSecret</key>
                <string>{your_psk}</string>
                <key>UseConfigurationAttributeInternalIPSubnet</key>
                <integer>0</integer>
            </dict>
            <key>IPv4</key>
            <dict>
                <key>OverridePrimary</key>
                <integer>1</integer>
            </dict>
            <key>PayloadDescription</key>
            <string>Configures VPN settings</string>
            <key>PayloadDisplayName</key>
            <string>VPN</string>
            <key>PayloadIdentifier</key>
            <string>com.apple.vpn.managed.FBFBDEF8-5B16-4863-91C1-7E2A68F848A3</string>
            <key>PayloadType</key>
            <string>com.apple.vpn.managed</string>
            <key>PayloadUUID</key>
            <string>425A1628-E99B-4547-966E-5B967CF1F5EA</string>
            <key>PayloadVersion</key>
            <real>1</real>
            <key>Proxies</key>
            <dict>
                <key>HTTPEnable</key>
                <integer>0</integer>
                <key>HTTPSEnable</key>
                <integer>0</integer>
            </dict>
            <key>UserDefinedName</key>
            <string>JP</string>
            <key>VPNType</key>
            <string>IKEv2</string>
            <key>VendorConfig</key>
            <dict/>
        </dict>
    </array>
    <key>PayloadDisplayName</key>
    <string>IKEv2</string>
    <key>PayloadIdentifier</key>
    <string>C7918ABA-8DE8-40ED-A3AE-994CD40ACE22</string>
    <key>PayloadRemovalDisallowed</key>
    <false/>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadUUID</key>
    <string>9697F3C2-FF20-4981-A0C4-AA36BA78EEEA</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
</dict>
</plist>

把需要填写的参数填入,保存为.mobileconfig文件,放到任何能用safari访问到的地方用safari打开安装就好。

Mac新建VPN选择Cisco IPsec方式就行。

四、配置防火墙

1
2
3
4
5
iptables -A INPUT -p udp --dport 500 -j ACCEPT
iptables -A INPUT -p udp --dport 4500 -j ACCEPT         #这是strongswan用到的两个端口
echo 1 > /proc/sys/net/ipv4/ip_forward                  #打开网卡转发功能。要把客户端的请求转发到公网网卡
iptables -t nat -A POSTROUTING -s 172.0.0.0/24 -o eth0 -j MASQUERADE     #转发客户端请求。注意网段和ipsec.conf里要一致。网卡使用外网网卡,阿里云的话是eth1
iptables -A FORWARD -s 172.0.0.0/24 -j ACCEPT            #允许转发

到此时,可以用客户端连到服务器试试能否正常连接。

五、配置中转

中转有两种方案:一种是国内ip直连,国外走代理。另一种是默认直连,黑名单走代理。这里选择前一种,因为对我个人来说,有些国外网站代理比直连要快。

服务器打开ss: nohup ss-server -u -A -c /etc/shadowsocks-libev/config.json &

国内VPS:

nohup ss-redir -c /etc/shadowsocks-libev/config.json -u -A -l 1080 -b 0.0.0.0 &

nohup ss-tunnel -c /etc/shadowsocks-libev/config.json -l 5353 -L 8.8.8.8:53 -u -A &

-u 表示打开udp转发。-A表示OTA认证,安全性更高

防火墙配置参数此脚本:(原理是下载国内ip段,生成ipset列表,访问国内ip时直连,其它ip转到ss-redir的端口)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/sh

#自动翻墙脚本,配合shadowsocks-libev的ss-redir使用。需要ipset(sudo apt-get install ipset)

server_IP=123.45.67.89

[ -r chnroute.txt ] || curl 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | grep ipv4 | grep CN | awk -F\| '{ printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > chnroute.txt

sudo iptables -t nat -N SHADOWSOCKS

sudo iptables -t nat -A SHADOWSOCKS -d $server_IP -j RETURN

# 内网网段
sudo iptables -t nat -A SHADOWSOCKS -d 0.0.0.0/8 -j RETURN
sudo iptables -t nat -A SHADOWSOCKS -d 10.0.0.0/8 -j RETURN
sudo iptables -t nat -A SHADOWSOCKS -d 127.0.0.0/8 -j RETURN
sudo iptables -t nat -A SHADOWSOCKS -d 169.254.0.0/16 -j RETURN
sudo iptables -t nat -A SHADOWSOCKS -d 172.16.0.0/12 -j RETURN
sudo iptables -t nat -A SHADOWSOCKS -d 192.168.0.0/16 -j RETURN
sudo iptables -t nat -A SHADOWSOCKS -d 224.0.0.0/4 -j RETURN
sudo iptables -t nat -A SHADOWSOCKS -d 240.0.0.0/4 -j RETURN

# 创建ipset列表
sudo ipset create chnroute hash:net
cat chnroute.txt | sudo xargs -I ip ipset add chnroute ip

sudo iptables -t nat -A SHADOWSOCKS -m set --match-set chnroute dst -j RETURN  # 国内ip表直连
sudo iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-ports 1080  #其他连接走ss转发

sudo iptables -t nat -A OUTPUT -p tcp -j SHADOWSOCKS #所以请求经过SHADOWSOCKS规则处理 # 这条命令对于我来说没用,我使用的是下边的命令 #有知道为啥无效的朋友请告知

iptables -t nat -A PREROUTING -s 172.0.0.0/24 -p tcp -j SHADOWSOCKS # 172xx为vpn客户端的网段
iptables -t nat -A PREROUTING -s 172.0.0.0/24 -p udp -j SHADOWSOCKS

六、配置dnsmasq

centos可以yum直接安装,其它系统请自行搜索

配置dnsmasq:

/etc/dnsmasq.conf最后一行conf-dir=/etc/dnsmasq.d取消注释

新建/etc/dnsmasq.d目录。里边新建两个文件default.confgfwlist.conf

default.conf里只需要一句话: server=114.114.114.114 表示默认上游DNS

gfwlist.conf初次可以用下边的脚本生成。脚本来自这里。注意端口和IP要设置成你需要的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import urllib2 
import re
import os
import datetime
import base64
import shutil
 
mydnsip = '127.0.0.1'
mydnsport = '5353'

# the url of gfwlist

baseurl = 'https://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt'
# match comments/title/whitelist/ip address

comment_pattern = '^\!|\[|^@@|^\d+\.\d+\.\d+\.\d+'
domain_pattern = '([\w\-\_]+\.[\w\.\-\_]+)[\/\*]*' 
tmpfile = '/tmp/gfwlisttmp'
# do not write to router internal flash directly

outfile = '/tmp/gfwlist.conf'
rulesfile = '/etc/dnsmasq.d/gfwlist.conf'
 
fs =  file(outfile, 'w')
fs.write('# gfw list ipset rules for dnsmasq\n')
fs.write('# updated on ' + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + '\n')
fs.write('#\n')
 
print 'fetching list...'
content = urllib2.urlopen(baseurl, timeout=15).read().decode('base64')

# write the decoded content to file then read line by line

tfs = open(tmpfile, 'w')
tfs.write(content)
tfs.close()
tfs = open(tmpfile, 'r')

print 'page content fetched, analysis...'

# remember all blocked domains, in case of duplicate records

domainlist = []

for line in tfs.readlines():    
    if re.findall(comment_pattern, line):
        print 'this is a comment line: ' + line
        #fs.write('#' + line)

    else:
        domain = re.findall(domain_pattern, line)
        if domain:
            try:
                found = domainlist.index(domain[0])
                print domain[0] + ' exists.'
            except ValueError:
                print 'saving ' + domain[0]
                domainlist.append(domain[0])
                fs.write('server=/.%s/%s#%s\n'%(domain[0],mydnsip,mydnsport))
                #fs.write('ipset=/.%s/gfwlist\n'%domain[0])         #因为我采用的时国外ip走代理的方式,所以不需要这行。

        else:
            print 'no valid domain in this line: ' + line
                    
tfs.close() 
fs.close();

print 'moving generated file to dnsmasg directory'
shutil.move(outfile, rulesfile)

print 'restart dnsmasq...'
print os.popen('/etc/init.d/dnsmasq restart').read()

print 'done!'

gfwlist的网址访问不到的话,可以在海外VPS上生成gfwlist.conf后复制回国内VPS。

service dnsmasq start命令开启dnsmasq服务。注意配置strongswan的DNS为本机的公网IP。

最好修改/etc/resolve.conf文件,把默认的DNS改为127.0.0.1.

至此,用手机客户端测试,ip138.com显示IP为阿里云,whatismyip.com显示IP为海外VPS。

如果全部按照我上文搭建中转服务器,现在有个问题是国内这台服务器是无法跨越长城的。因为这台服务器另有他用,而且我也没有让它也支持跨越长城的需求,所以这个我就不折腾了。