Stunnel隐藏OpenVPN流量实现科学上网

简介

众所周知的原因,在海外直接搭建 OpenVPN 根本无法使用(TCP 模式),或者用段时间就被墙了(UDP 模式)。本文主要介绍如何通过 Stunnel 隐藏 OpenVPN 流量,使其看起来像普通的 SSL 协议传输,从而绕过 gfw。

Stunnel 分为客户端和服务端,客户端负责接收用户 OpenVPN 客户端流量并转化成 SSL 协议加密数据包,然后转发给 Stunnel 服务端,实现 SSL 协议数据传输,服务端然后将流量转化成 OpenVPN 流量传输给 OpenVPN 服务端。因此我们可以在国内搭 Stunnel 客户端,国外搭 Stunnel 服务端。OpenVPN + Stunnel 整体架构如下:

Stunnel 隐藏 OpenVPN 流量具体过程

1. 搭建 OpenVPN 服务端

关于 OpenVPN 的搭建及使用在这里不多说了,可以查看我之前的博文。这里要说明的是,Stunnel 不支持 udp 流量转换,所以 OpenVPN 需要以 TCP 模式运行。下面为 OpenVPN 服务端 TCP 模式的 server.conf 配置示例:

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
####################################################################
# 针对多客户端的OpenVPN 2.4的服务器端配置文件示例
#
# 本文件用于多客户端<->单服务器端的OpenVPN服务器端配置
#
# OpenVPN也支持单机<->单机的配置(更多信息请查看网站上的示例页面)
#
# 该配置支持Windows或者Linux/BSD系统。此外,在Windows上,记得将路径加上双引号,
# 并且使用两个反斜杠,例如:"C:\\Program Files\\OpenVPN\\config\\foo.key"
#
# '#' or ';'开头的均为注释内容
###################################################################

# OpenVPN监听本机的哪些IP地址,该命令是可选的,如果不设置,则默认监听本机的所有IP地址
;local a.b.c.d
# 监听的端口号
port 1194
# 服务端用的协议tcp/udp,udp能快点,所以我选择udp
proto udp

# 默认使用路由模式,tap是桥接模式
# 指定OpenVPN创建的通信隧道类型
# "dev tun"将会创建一个路由IP隧道
# "dev tap"将会创建一个以太网隧道
# 如果你是以太网桥接模式,并且提前创建了一个名为"tap0"的与以太网接口进行桥接的虚拟接口,则你可以使用"dev tap0"
# 如果你想控制VPN的访问策略,你必须为TUN/TAP接口创建防火墙规则
# 在非Windows系统中,你可以给出明确的单位编号(unit number),例如"tun0"
# 在Windows中,你也可以使用"dev-node"
# 在多数系统中,除非你部分禁用或者完全禁用了TUN/TAP接口的防火墙,否则VPN将不起作用
;dev tap
dev tun

# 如果你想配置多个隧道,你需要用到网络连接面板中TAP-Win32适配器的名称例如"MyTap")
# 在XP SP2或更高版本的系统中,你可能需要有选择地禁用掉针对TAP适配器的防火墙
# 通常情况下,非Windows系统则不需要该指令。
;dev-node MyTap

# CA根证书路径
ca /etc/openvpn/server/certs/ca.crt
# OpenVPN服务端证书路径
cert /etc/openvpn/server/certs/server.crt
# OpenVPN服务端密钥路径
key /etc/openvpn/server/certs/server.key
# Diffie-Hellman算法密钥文件路径
dh /etc/openvpn/server/certs/dh.pem

# 出于SSL/TLS之外更多的安全考虑,创建一个"HMAC 防火墙"可以帮助抵御DoS攻击和UDP端口淹没攻击。
# 你可以使用以下命令来生成:
# openvpn --genkey --secret ta.key
# 服务器和每个客户端都需要拥有该密钥的一个拷贝。
# 第二个参数在服务器端应该为'。
# tls-auth key,参数0可以省略,如果不省略,那么客户端配置文件相应的参数tls-auth该配成 1。如果省略,那么客户端不需要 tls-auth 配置
tls-auth /etc/openvpn/server/certs/ta.key 0

# 选择一个密码加密算法。
# 该配置项也必须复制到每个客户端配置文件中。
;cipher BF-CBC # Blowfish (默认)
;cipher AES--CBC # AES
;cipher DES-EDE3-CBC # Triple-DES

# 该网段为OpenVPN虚拟网卡网段,不要和内网网段冲突即可。OpenVPN默认使用10.8.0.0/24网段为客户端分配IP地址
server 10.8.0.0 255.255.255.0
# 指定用于记录客户端和虚拟IP地址的关联关系的文件,当重启OpenVPN时,再次连接的客户端将分配到与上一次分配相同的虚拟IP地址
ifconfig-pool-persist ipp.txt

# 主DNS服务器配置,可以根据需要指定其他DNS
push "dhcp-option DNS 8.8.8.8"
# 备DNS服务器配置,可以根据需要指定其他DNS
push "dhcp-option DNS 8.8.4.4"

# 推送路由信息到客户端,告诉客户端连接172.18.0.0/16这个网段的流量通过openvpn转发,以允许客户端能够连接到服务端背后的其他私有子网;
# 其中172.18.0.0/16网段是你要连接的openvpn服务端的私网ip地址段,类似于局部代理;
# 使客户端机器在浏览器访问其他正常网页时由本地网卡出去,从而达到不影响本地网络的网速。
push "route 172.18.0.0 255.255.0.0"

# 客户端所有流量都通过open VPN虚拟网卡转发,类似于全局代理,这样客户端在浏览器访问其他正常网站时;
# 也是通过OpenVPN虚拟网卡出去访问,会严重影响客户端访问正常网页的网速
;push "redirect-gateway def1"

# 该指令仅针对以太网桥接模式。
# 首先,你必须使用操作系统的桥接能力将以太网网卡接口和TAP接口进行桥接。
# 然后,你需要手动设置桥接接口的IP地址、子网掩码;
# 在这里,我们假设为10.。
# 最后,我们必须指定子网的一个IP范围(例如从10.8.0.50开始,到10.8.0.100结束),以便于分配给连接的客户端。
# 如果你不是以太网桥接模式,直接注释掉这行指令即可。
;server-bridge 10.8.0.4 255.255.255.0 10.8.0.50 10.8.0.100

# 该指令仅针对使用DHCP代理的以太网桥接模式,
# 此时客户端将请求服务器端的DHCP服务器,从而获得分配给它的IP地址和DNS服务器地址。
# 在此之前,你也需要先将以太网网卡接口和TAP接口进行桥接。
# 注意:该指令仅用于OpenVPN客户端,并且该客户端的TAP适配器需要绑定到一个DHCP客户端上。
;server-bridge

# 允许一个用户多个终端连接
# 如果多个客户端可能使用相同的证书/私钥文件或Common Name进行连接,那么你可以取消该指令的注释。
# 建议该指令仅用于测试目的。对于生产使用环境而言,每个客户端都应该拥有自己的证书和私钥。
# 如果你没有为每个客户端分别生成Common Name唯一的证书/私钥,你可以取消该行的注释(但不推荐这样做)。
;duplicate-cn

# keepalive指令将导致类似于ping命令的消息被来回发送,以便于服务器端和客户端知道对方何时被关闭。
# 每10秒钟ping一次,如果120秒内都没有收到对方的回复,则表示远程连接已经关闭。
keepalive 10 120

# 在VPN连接上启用压缩。
# 如果你在此处启用了该指令,那么也应该在每个客户端配置文件中启用它。
comp-lzo

# 允许并发连接的客户端的最大数量
;max-clients

# 去掉该指令的注释将允许不同的客户端之间相互"可见"(允许客户端之间互相访问)。
# 该选项允许连接openvpn的客户端直接通讯而不经过openvpn服务端网关。
# 默认情况下,客户端只能"看见"服务器。为了确保客户端只能看见服务器,你还可以在服务器端的TUN/TAP接口上设置适当的防火墙规则。
client-to-client

# 持久化选项可以尽量避免访问那些在重启之后由于用户权限降低而无法访问的某些资源。
persist-key
persist-tun

# OpenVPN进程启动用户,OpenVPN用户在安装完openvpn安装包后就自动生成了
# 在完成初始化工作之后,降低OpenVPN守护进程的权限是个不错的主意。
# 该指令仅限于非Windows系统中使用。
user openvpn
group openvpn

# 默认情况下,日志消息将写入syslog(在Windows系统中,如果以服务方式运行,日志消息将写入OpenVPN安装目录的log文件夹中)。
# 你可以使用log或者log-append来改变这种默认情况。
# "log"方式在每次启动时都会清空之前的日志文件。
# "log-append"这是在之前的日志内容后进行追加。
# 你可以使用两种方式之一(但不要同时使用)。
;log /var/log/openvpn/openvpn.log
log-append /var/log/openvpn/openvpn.log
# 输出一个简短的状态文件,用于显示当前的连接状态,该文件每分钟都会清空并重写一次。
status /var/log/openvpn/openvpn-status.log

# 为日志文件设置适当的冗余级别(~)。冗余级别越高,输出的信息越详细。
# 0 表示静默运行,只记录致命错误。
# 4 表示合理的常规用法。
# 5 可以帮助调试连接错误。
# 9 表示极度冗余,输出非常详细的日志信息。
verb 3

# 重复信息的沉默度。
# 相同类别的信息只有前20条会输出到日志文件中。
;mute

# 当服务端重新启动时,通知客户端可以自动重新连接。
# 当proto使用tcp时,需要注销该行配置,否则会导致服务端无法启动
explicit-exit-notify 1

# 为指定的客户端分配指定的IP地址,或者客户端背后也有一个私有子网想要访问VPN,
# 那么你可以针对该客户端的配置文件使用ccd子目录。
# (简而言之,就是允许客户端所在的局域网成员也能够访问VPN)
# 举个例子:假设有个Common Name为/255.255.255.248。
# 首先,你需要去掉下面两行指令的注释:
;client-config-dir ccd
;route 192.168.40.128 255.255.255.248
# 然后创建一个文件ccd/Thelonious,该文件的内容为:
# iroute 192.168.40.128 255.255.255.248

# 这样客户端所在的局域网就可以访问VPN了。
# 注意,这个指令只能在你是基于路由、而不是基于桥接的模式下才能生效。
# 比如,你使用了"dev tun"和"server"指令。
# 再举个例子:假设你想给Thelonious分配一个固定的IP地址10.。
# 首先,你需要去掉下面两行指令的注释:
;client-config-dir ccd
;route 10.9.0.0 255.255.255.252
# 然后在文件ccd/Thelonious中添加如下指令:
# ifconfig-push 10.9.0.1 10.9.0.2
#

# 如果你想要为不同群组的客户端启用不同的防火墙访问策略,你可以使用如下两种方法:
# # ()运行多个OpenVPN守护进程,每个进程对应一个群组,并为每个进程(群组)启用适当的防火墙规则。
# # () (进阶)创建一个脚本来动态地修改响应于来自不同客户的防火墙规则。
# # 关于learn-address脚本的更多信息请参考官方手册页面。
;learn-address ./script

2. Stunnel 服务端安装配置

安装配置 Stunnel 服务端(海外节点)

安装Stunnel 服务端,执行以下命令:

1
2
3
4
# yum -y install stunnel
# cd /etc/stunnel
# openssl req -new -x509 -days 3650 -nodes -out stunnel.pem -keyout stunnel.pem
# chmod 600 /etc/stunnel/stunnel.pem

修改Stunnel 服务端的配置文件 stunnel.conf,执行以下命令:

1
# vim stunnel.conf   # 编辑配置文件stunnel.conf

stunnel.conf 填入如下内容:

1
2
3
4
5
6
7
pid = /var/run/stunnel.pid
output = /var/log/stunnel.log
client = no
[openvpn]
accept = 443
connect = 127.0.0.1:4001
cert = /etc/stunnel/stunnel.pem

说明:

accept = 443 # Stunnel 服务端监听端口
connect = 127.0.0.1:4001 # OpenVPN 服务端 IP 地址和端口

使用 systemd 启动 Stunnel 服务端

为了管理方便,我们使用 systemd 管理 Stunnel 服务,编辑一个 systemd unit 的管理文件,执行以下命令:

1
# vim /lib/systemd/system/stunnel.service

添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Unit]
Description=SSL tunnel for network daemons
After=network.target
After=syslog.target

[Install]
WantedBy=multi-user.target
Alias=stunnel.target

[Service]
Type=forking
ExecStart=/usr/bin/stunnel /etc/stunnel/stunnel.conf
ExecStop=/usr/bin/killall -9 stunnel

# Give up if ping don't get an answer
TimeoutSec=600

Restart=always
PrivateTmp=false

启动 Stunnel 服务端:

1
2
systemctl start stunnel.service
systemctl enable stunnel.service

3. Stunnel 客户端安装配置

Stunnel 的客户端安装和服务器一样,同样的软件,既可以作为客户端,也可以作为服务端,只是配置不同而已。

安装配置 Stunnel 客户端(国内节点)

1
2
3
4
5
6
7
8
9
10
11
12
13
yum -y install stunnel
cd /etc/stunnel
scp .... # 将服务端的证书 stunnel.pem 拷贝到这里
chmod 600 /etc/stunnel/stunnel.pem
vim stunnel.conf 填入如下内容:
pid=/var/run/stunnel.pid
output=/var/log/stunnel.log
client = yes

[openvpn]
accept=8443
connect=stunnel_server_ip:443
cert = /etc/stunnel/stunnel.pem

说明:

accept=8443 # Stunnel 客户端监听端口
stunnel_server_ip:443 # stunnel 服务端 ip 及端口

使用 systemd 启动 Stunnel 客户端

这里前面同服务端的操作过程,不再赘述。
启动 Stunnel 客户端:

1
2
systemctl start stunnel.service
systemctl enable stunnel.service

4. 使用 OpenVPN 连接 Stunnel

Stunnel + OpenVPN 都配好后,就可以使用 OpenVPN 客户端实现科学上网了,需要注意的是 OpenVPN 客户端现在需要连接的是 Stunnel 客户端,不再是直接连接 OpenVPN 服务端。

相关文档

https://github.com/Xaqron/stunnel