V2Ray反代版中转服务器搭建

JiaoKan 发布于 2024-03-14 28 次阅读


💬 前言

不少的公司、机构、学校都有所谓的“内部网络”,只允许来自内部的访问,比如说公司的代码库、学校的校园网等等,但是随着网络的发展、远程办公、协作的需求渐渐变多,如何在外部访问内部网络,便成为了一个重要的课题.

VPN 技术就是在这种需求下诞生的,给出了一个安全的办法来解决这种需求.具体而言,举个简单的例子,A 公司设置了一个具有公网 IP 的服务器,架设了 VPN,并且把这个服务器连接到公司内网.这个时候,在外部的设备就可以使用 VPN 连接这个服务器,然后这个服务器就可以充当一个“代理”,代理所有的流量,这样在外部访问内网的流量就可以转由这台机器来完成,从而让我们可以在外部访问内网,大概就像下面这张图:

file

但是大多数情况下,没有这么一个 VPN 服务器,而且我们也没有公网 IP......

着似乎十分脱离生活,但是仔细一想,这其实在生活中,有很多需求和这个极其相似,举几个例子:

  • 人在家里,在公司有电脑,我想通过在公司的电脑访问公司内网
  • 人在国外,在国内家中有电脑,想要通过国内的电脑访问一些国内特定的服务(典型的就比如说 QQ 音乐、网易云啥的)
  • 人在国内,在海外有 VPS,但无法访问 ChatGPT,不过我有朋友在国外,想要通过他来访问(这条可以扩展到 Netflix,Spotify 等等需要原生 IP 的服务)
  • ...

这些需求,其实有一套非常不错的解决方案——反向代理,你所需要的仅仅是一台谁都可以访问的公网服务器.

⬅️ 反向代理

假设我有一台 A 设备,在我需要的网络环境下工作,但是没有公网 IP,或者由于各种原因,比如防火墙等,不能被外界直接访问,我现在有一台 C 设备,在外部网络,我想通过 A 来帮我们代理,从而访问特定服务.

这个时候,我们就需要有一个 B 服务器,同时可以被 A 和 C 访问,它将会进行反向代理,也就是代理 A 服务器,C 在连接 B ,将其作为代理服务的时候,其实 B 会把所有流量转发给 A,这样就可以解决我们的需求.

鉴于我们的 A 设备并没有公网 IP,所以说不能简单地用类似 Nginx 这样的工具在 B 上假设反向代理,我们需要做一个“内网穿透”.

最终我们要达成的架构如下图所示:

file

数据的流向:
请求: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 自带的反向代理功能和路由功能来实现反向代理.

🔍 原理详解

先上架构图:

file

如果你对 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 合二为一,靠上一段中的标识来控制路由.

📚 参考资料

官方配置文档
官方反向代理教程

此作者没有提供个人介绍。
最后更新于 2024-03-14