💬 前言
不少的公司、机构、学校都有所谓的“内部网络”,只允许来自内部的访问,比如说公司的代码库、学校的校园网等等,但是随着网络的发展、远程办公、协作的需求渐渐变多,如何在外部访问内部网络,便成为了一个重要的课题.
VPN 技术就是在这种需求下诞生的,给出了一个安全的办法来解决这种需求.具体而言,举个简单的例子,A 公司设置了一个具有公网 IP 的服务器,架设了 VPN,并且把这个服务器连接到公司内网.这个时候,在外部的设备就可以使用 VPN 连接这个服务器,然后这个服务器就可以充当一个“代理”,代理所有的流量,这样在外部访问内网的流量就可以转由这台机器来完成,从而让我们可以在外部访问内网,大概就像下面这张图:
但是大多数情况下,没有这么一个 VPN 服务器,而且我们也没有公网 IP......
着似乎十分脱离生活,但是仔细一想,这其实在生活中,有很多需求和这个极其相似,举几个例子:
- 人在家里,在公司有电脑,我想通过在公司的电脑访问公司内网
- 人在国外,在国内家中有电脑,想要通过国内的电脑访问一些国内特定的服务(典型的就比如说 QQ 音乐、网易云啥的)
- 人在国内,在海外有 VPS,但无法访问 ChatGPT,不过我有朋友在国外,想要通过他来访问(这条可以扩展到 Netflix,Spotify 等等需要原生 IP 的服务)
- ...
这些需求,其实有一套非常不错的解决方案——反向代理,你所需要的仅仅是一台谁都可以访问的公网服务器.
⬅️ 反向代理
假设我有一台 A 设备,在我需要的网络环境下工作,但是没有公网 IP,或者由于各种原因,比如防火墙等,不能被外界直接访问,我现在有一台 C 设备,在外部网络,我想通过 A 来帮我们代理,从而访问特定服务.
这个时候,我们就需要有一个 B 服务器,同时可以被 A 和 C 访问,它将会进行反向代理,也就是代理 A 服务器,C 在连接 B ,将其作为代理服务的时候,其实 B 会把所有流量转发给 A,这样就可以解决我们的需求.
鉴于我们的 A 设备并没有公网 IP,所以说不能简单地用类似 Nginx 这样的工具在 B 上假设反向代理,我们需要做一个“内网穿透”.
最终我们要达成的架构如下图所示:
数据的流向:
请求:C->B->A->内网服务
回复:内网服务->A->B->C
🧐 技术选型
接下来我们就要决定用什么技术来实现我们这套架构了.
其实这里面可能的组合非常非常的多,这取决于不同的情况.
比如你是想翻墙,但是在境外没有服务器,反而有个境内的,哪里A-B
这一段就可以选用各种翻墙协议,保障隐蔽性及安全性,B-C
这一段就可以选择别的各种代理协议.
再比如你是想听受版权限制,只能在国内播放的音乐,B 在国外,那么你A-B
这一段就需要使用翻墙协议,B-C
段可以选择别的.
协议的选择也有很多种,比如经典的内网穿透反向代理的协议就有诸如 Frp、Ngrok 等等,翻墙的协议,加上各种技术组合,那更是浩如烟海.
但是,有这么一个“平台”,V2Ray,内部集成了各种协议,任君挑选,而且功能完善使用简单,这里要纠正一个迷思, V2Ray 不是一个协议,是一个平台,这个平台支持各种协议,VMess 呢则是由这个平台官方团队自己开发的一个协议,这点要搞清楚.
而且,身边有这种需求的人,多半已经接触过一些 V2Ray 相关的知识了,所以这边选用这个平台来作为我们的基石.
在这之上,为了尽量满足各种需求,在A-B-C
的全链路上采用VMess+TLS+WebSocket
来实现数据交换,在A-B
之间使用 V2Ray 自带的反向代理功能和路由功能来实现反向代理.
🔍 原理详解
先上架构图:
如果你对 V2Ray 很熟悉的话,应该可以看懂,对于一知半解的呢,我这边给出几点关键的知识:
- V2Ray 中,Bridge 模块会主动尝试连接 Protal 模块
- 我们可以设置路由规则,将所有从 Bridge 发出的流量(尝试连接 Protal 的)转发给 tag 为 bridge 的出站模块,同理可以再 B 上把所有接收到的尝试连接 Protal 的流量转发给 Protal
- 主动了一个方向的连接,就不用再定义另一个方向的连接了(不深入探究的话,可以理解成建立了个双向连接,在这里)
然后结合上面的架构图,应该可以初探一二,也可以仔细研读下面给出的配置文件,还有官方文档,来更加深入地理解,并自己根据具体情况加以优化.
📃 配置文件
A 机器上面:
{
"log": {
"loglevel": "debug"
},
"reverse": {
"bridges": [
{
"tag": "bridge",
"domain": "tunnel.io"
}
]
},
"outbounds": [
{
"tag": "tunnel",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "example.com",
"port": 443,
"users": [
{
"id": "[UUID]",
"alterId": 0
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"wsSettings": {
"path": "/dic"
}
}
},
{
"tag": "out",
"protocol": "freedom",
"settings": {}
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": [
"bridge"
],
"domain": [
"full:tunnel.io"
],
"outboundTag": "tunnel"
},
{
"type": "field",
"inboundTag": [
"bridge"
],
"outboundTag": "out"
}
]
}
}
B 机器上面:
{
"log": {
"loglevel": "debug"
},
"reverse": {
"portals": [
{
"tag": "portal",
"domain": "tunnel.io"
}
]
},
"inbounds": [
{
"tag": "tunnel",
"port": 10000,
"listen": "127.0.0.1",
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "[Another UUID]",
"alterId": 0
}
]
},
"streamSettings": {
"network": "ws",
"wsSettings": {
"path": "/dict"
}
}
},
{
"tag": ")",
"port": 10001,
"listen": "127.0.0.1",
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "[UUID]",
"alterId": 0
}
]
},
"streamSettings": {
"network": "ws",
"wsSettings": {
"path": "/dic"
}
}
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": [
"interconn"
],
"outboundTag": "portal"
},
{
"type": "field",
"inboundTag": [
"tunnel"
],
"domain": [
"full:tunnel.io"
],
"outboundTag": "portal"
}
]
}
}
B 机器上的 WebSocket 转发设置(以 Nginx 为例):
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
root /var/www/html;
location /dict {
if ($http_upgrade != "websocket") {
return 404;
}
proxy_redirect off;
proxy_pass http://127.0.0.1:10000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# Show real IP in v2ray access.log
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /dic {
if ($http_upgrade != "websocket") {
return 404;
}
proxy_redirect off;
proxy_pass http://127.0.0.1:10001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# Show real IP in v2ray access.log
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
C 机器上面一般是有图形化界面,根据你在 B 机器上 tag 为 tunnel 的 inbound 的设置来就行.
要注意的是,上面的配置文件中tunnel.io
不一定是要真实存在的域名,这个东西的主要作用是标识 bridge 和 protal 之间的流量,以便设置路由并控制转发,但是请一定要保持一致.
此外,如果你有兴趣,也可以试着把 B 机器上的两个 outbounds 合二为一,靠上一段中的标识来控制路由.
Comments NOTHING