打通大内网第一期 无公网部署https和反向代理
qaz741wsd856 Lv2

前言

本教程不适合小白,本人因为水平有限,也无法解决大家遇到的问题,一旦出现问题还得请各位自行摸索。

首要条件:NAT类型为NAT1,即fullcone;光猫桥接或开启dmz;路由器能刷机或者开启upnp/dmz
建议:路由器能够控制具体的端口开放与否,而不是只能全开或全关。

温馨提示:

本方案不保证成功率和稳定性,而且操作比较繁琐,如果你可以通过社会工程学的方式获取公网ipv4,请尽量尝试;如果你在外访问时可以使用ipv6,请尝试ipv6方案。如果你对以上概念完全没有印象,建议学习基本的网络知识后再来看本教程,再次提醒,本教程不适合小白,我也没有能力解决各位的问题。

常见穿透方案及其局限性

我的需求很简单,能随时随地访问家里的nas,我部署的服务既有需要大带宽的alist、jellyfin等,也有对延迟敏感的teamspeak、远程桌面。目前常见的内网穿透方案有如下几种:

  1. 公网ip
    首选方案肯定是向运营商要公网IP,如果是是电信和联通可能还有希望,其它运营商或是校园网基本没戏。
    虽然现在家庭宽带的ipv6基本普及,但例如公司、出租屋、酒店等场景基本没有覆盖,况且我的小白朋友压根不会也不想开启。
  2. frp或vpn
    如果自己租服务器搭建,那么同时能满足我对带宽和延迟需求的服务器价格实在太感人;
    使用别人搭建好的服务,的确能找到价格很低的,但是稳定性堪忧。
  3. 各种P2P打洞组网软件
    访问者既要下软件、又要注册账号,对于小白朋友还是太麻烦了;
    部分访问场景是NAT4,没法直连,走代理的体验不佳。

那么有没有一种方案,既能满足大带宽、低延迟的需求,同时还免费,甚至稳定性也还可以呢?我经过苦苦搜寻,发现STUN穿透能完美满足我的需求,唯一的缺点就是…非常非常折腾。

NATMAP与lucky:

目前有STUN穿透功能的软件主要有NATMAPlucky。在这两款软件都能正常运行的前提下,如果只需通过浏览器访问nas上的服务,那么lucky使用起来容易的多;如果需要实现更加复杂的玩法,那么NATMAP更加灵活。然而NATMAP的web ui仅支持官方OpenWrt固件,其他固件、平台只能通过命令行调用,如果要建立多个隧道,还得手动管理各个实例,较为繁琐,而lucky在各个平台都有web ui;同时lucky除了STUN穿透之外,还有各种实用功能,可以省去安装一堆软件的麻烦,故本教程使用lucky。

教程以OpenWrt为例,其它平台请参考官方文档
如果在路由器以外的设备安装,需要开启路由器的upnp或者dmz。
请参考使用Lucky的STUN内网穿透利用UPNP和NAT1在公网打洞并配置伪DDNS 使用Lucky的STUN内网穿透利用UPNP和NAT1在公网打洞并配置伪DDNS

第一步:安装lucky

登陆openwrt后台后,依次点击系统​-文件传输​-选择文件​,找到下载的lucky插件,之后点击上传,将lucky和luci-app-lucky上传。之后在下方的上传文件列表​中,先安装lucky,再安装luci-app-lucky。

image

image

第二步:打开lucky后台并修改用户名和密码

点击服务​ - lucky​ - admin panel​右侧的网址,打开后台,初次使用时,默认用户名密码均为666。

image

之后记得在设置里修改账号密码。

第三步,开启STUN内网穿透

点击STUN内网穿透​ - 穿透规则列表​ - 添加穿透规则​具体设置如下

​​image​​

请注意,如果在路由器之外的设备上安装,需要开启nat-pmp​。
点击保存,稍等一会儿应该就能看见公网ip和端口了,点击公网地址即可复制。

​​image​​

如果不出意外,在在外网设备的浏览器地址栏中粘贴所复制的公网地址(ip:端口),应该就能访问成功了。如果无法访问成功,请编辑刚才创建的穿透规则,手动指定穿透通道监听端口,并在防火墙设置里开启穿透通道监听端口​的端口。至此我们便初步完成了内网穿透的基本操作,在外网环境中,只要输入此公网地址,即可访问绑定的内网服务。

第四步,将公网地址绑定到域名

原理:

使用STUN穿透时,不光ip地址会变,端口也会变,这使得我们无法通过传统ddns来绑定地址,所幸我们可以利用Cloudflare回源和跳转规则。

回源

我们可以设置一条回源规则,令外网设备访问规则内的域名时,Cloudflare的cdn服务器会将流量转发到我们指定的地址和端口,并且这一切对于访问者来说是不可见的,他在浏览器地址栏里只能看到域名。

​​image​​

这个方案看起来很完美,只可惜存在一个致命的缺陷:当我们在通过域名访问时,所有流量都会从CloudFlare的cdn服务器中转,而免费计划中,只能使用海外的cdn服务器,从国内访问速度非常感人。

跳转

本教程中选择使用重定向规则进行跳转。在我们设置里跳转规则后,外网设备通过域名访问时,会先从Cloudflare的cdn上得到一条跳转响应,然后浏览器会自动跳转到规则中设定的地址。

​​image​​

使用这种方法,流量不会经过cdn服务器,访问者在浏览器地址栏里看到的是跳转后的地址。

Cloudflare 的页面规则即将弃用,建议考虑替代方案


替代方案

Cloudflare的页面规则即将弃用,需要考虑其他替代方案了,我目前想到的有:

  1. 通过短链接平台跳转
    只适合需穿透服务较少、无需ssl的场景。
  2. 在服务器上部署Lucky的跳转服务
    前提是你得有一个服务器,参考使用Lucky在公网IPv4环境自建重定向和中转服务,固定STUN的反代端口
  3. 在本地端Lucky上配置跳转,再用传统方法穿透出去
    直接用cf tunnel或者给ipv6套cf的cdn,速度可能较慢;
    白嫖公益frp的速度会比较快,但如不备案则需要输入端口号,如果备案后用HTTP隧道,则只能用路径跳转;
    使用国内cdn的ipv6回源也行,不过也需要备案。
  4. 使用cf的重定向规则
    使用上跟页面规则差不多,但免费计划用不了正则表达式,所以没页面规则灵活。
  5. 使用cf worker跳转
    使用上相比重定向规则更简单,但速度慢很多,不建议。

如果有条件,建议选择前三种,跳转的速度会快一些,国内访问Cloudflare的速度还是不太理想。
本文以cf的重定向规则为演示。


使用重定向规则进行跳转

解析域名到Cloudflare

首先需要将你的域名解析到Cloudflare,网上的教程很多,开头的链接里也有教,在此不做赘述。

在网站概览界面向下翻,在右侧可以看到区域id​,将其保存下来备用。

image

添加一条a类型的记录到你需要访问的域名,本教程里名称填入*,代表所有的未指定的二级域名都会返回这条记录,ipv4地址随便填一个有效的即可,代理状态勾选,ttl保持自动,填写完成后保存。

image

配置重定向规则

点击左侧菜单的规则​-重定向规则​,在单一重定向​页面下点击创建规则​,规则名称随意设置。

当传入请求匹配时...​处选择自定义筛选表达式​,字段选择主机名​,运算符选择等于​,​需填入自行设置的用于跳转的域名;示例为test.yourdomain.com​,test​为自定义的二级域名前缀,yourdomain.com​换成你的顶级域名。

URL 重定向​处选择类型为动态​并勾选保留查询字符串​,状态码选择302​或307​均可;表达式填concat("http://STUN的公网ip:公网端口", http.request.uri.path)​,即将示例中的183.6.66.666:6666​替换为在lucky中复制的地址。

请先不要点击部署

image

在保存之前先按下F12打开开发者工具,点击右侧开发者工具的网络​或者network​选项卡,再点击左侧Cloudflare页面中的部署​。
接下来可以在开发者工具中看到一串数字+字母的项,点击它,在标头​选项卡下复制并保存下它的请求网址​;然后切换到载荷​选项卡,右键第一行复制并保存它的值。

image

请求网址形如:

1
https://dash.cloudflare.com/api/v4/zones/区域id/rulesets/规则集id/rules/规则id

区域id​、规则集id​、规则id​保存下来备用。

image

保存的请求载荷形如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"id": "c7087a0fb757480xxxxxxxxxxxxxxxx",
"version": "1",
"action": "redirect",
"expression": "(http.host eq \"test.yourdomain.com\")",
"description": "redirect",
"last_updated": "2024-05-28T08:14:45.555123Z",
"ref": "c7087a0fb757480xxxxxxxxxxxxxxxx",
"enabled": true,
"action_parameters": {
"from_value": {
"status_code": 307,
"target_url": {
"expression": "concat(\"http://183.6.66.666:6666\", http.request.uri.path)"
},
"preserve_query_string": true
}
}
}

你的id​、ref​应该与规则id一致,description​就是之前设置的规则名称。
删去其中version​和last_updated​变量(也就是示例中的第3、5行)保存备用。

使用页面规则进行跳转

页面规则即将被弃用,建议考虑替代方案。

首先需要将你的域名解析到Cloudflare,网上的教程很多,开头的链接里也有教,在此不做赘述。

在网站概览界面向下翻,在右侧可以看到区域id​,将其保存下来备用。

​​image​​

添加一条a类型的记录到你需要访问的域名,本教程里名称填入*,代表所有的未指定的二级域名都会返回这条记录,ipv4地址随便填一个有效的即可,代理状态勾选,ttl保持自动,填写完成后保存。

​​image​​

接下来开始配置页面规则:

点击左侧的规则​ - 页面规则​,点击右侧的创建页面规则

url填入 你的域名/*​,注意不能有http或https。
示例为test.yourdomain.com/*​,test​为自定义的二级域名前缀,yourdomain.com​换成你的顶级域名。
选取设置选为转发URL​,状态代码选择301​。
目标URL填入http://STUN的公网ip:公网端口/$1​,即将示例中的183.6.66.666:6666​替换为在lucky中复制的地址。

请先不要点击保存

​​image​​

在保存之前先按下F12打开开发者工具,点击右侧开发者工具的网络​​或者network​​选项卡,再点击左侧Cloudflare页面中的保存和部署页面规则​​。
接下来可以在开发者工具中看到一串数字+字母的项,点击它,复制它的请求网址。

image

该请求网址形如:

1
https://dash.cloudflare.com/api/v4/zones/区域id/pagerules/规则id

其中区域id​应该与之前保存的相同,请把规则id​保存备用。如果你在打开开发者工具前已经点击了保存,请编辑刚才创建的页面规则,重复上述步骤。

第五步,利用webhook自动更新页面规则

lucky提供了webhook接口,可以在ip和端口发生变化时发送一条网络请求,调用Cloudflare的api来更改页面规则或是dns记录。

让我们返回lucky后台,找到刚才创建的STUN穿透规则,点击编辑,开启webhook(而不是全局webhook),按如下方式配置:

更新重定向规则

接口地址:

1
https://api.cloudflare.com/client/v4/zones/区域id/rulesets/规则集id/rules/规则id

请注意:这不是上一步抓到的请求网址,请用之前保存的区域id​、规则集id​、规则id​对以上链接进行替换。

请求方法:PATCH

请求头:

1
Authorization:Bearer API 令牌

要创建API令牌,点击Cloudflare控制台右上角-我的个人资料​,在左侧菜单点击API令牌​,页面中点击创建令牌​。

在令牌创建页面选择创建自定义令牌​。

权限和区域资源如图设置,区域资源第三列选择你要用于跳转的域名。
image
点击继续​-创建令牌​,直至出现以下界面:

image

复制第二行双引号里的内容(不带引号),粘贴到Lucky的Webhook的请求头中。


请求主体:
把之前保存的请求载荷删去last_updated​和version​后,将expression​中的ip:端口​替换为#{ipAddr}
你的id​、ref​应该与规则id一致,description​就是之前设置的规则名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"id": "c7087a0fb757480xxxxxxxxxxxxxxxx",
"action": "redirect",
"expression": "(http.host eq \"test.yourdomain.com\")",
"description": "redirect",
"ref": "c7087a0fb757480xxxxxxxxxxxxxxxx",
"enabled": true,
"action_parameters": {
"from_value": {
"status_code": 307,
"target_url": {
"expression": "concat(\"http://#{ipAddr}\", http.request.uri.path)"
},
"preserve_query_string": true
}
}
}

接口调用成功包含的字符串:"success": true​。

这些全部完成后点击Webhook手动触发测试​。注意:测试时的数据均为虚拟数据。 如果不成功,请先查看webhook里返回的内容是否很长且包含"success": true​,如果包含,那么复制那行并替换接口调用成功包含的字符串​里的内容;然后多试几次排查网络问题,再检查各参数设置,配置正确后点击保存。

等待lucky执行成功webhook后,在浏览器无痕模式中输入在重定向规则中设定的域名,即可跳转至对应的公网地址。如需收藏或分享链接,只需将浏览器中的ip:端口​ 部分重新替换回域名即可。

更新页面规则

页面规则即将弃用,请考虑替代方案
接口地址:

1
https://api.cloudflare.com/client/v4/zones/区域id/pagerules/规则id

请注意:这不是上一步抓到的请求网址,请用之前保存的区域id​和规则id​对以上链接进行替换。

请求方法:PUT​​

请求头:

1
2
X-Auth-Email: 你注册CF的邮箱
X-Auth-Key: Global API Key

请注意:Global API Key 在我的个人资料​- API令牌​中查看,这不是自己创建的api令牌,而是在下方的API密钥​中点击查看

也可自行创建API令牌,点击创建令牌

编辑权限和区域规则如下图所示,其中区域资源第三列选择你用于跳转到域名。

image

点击继续​-创建令牌​,直至出现以下界面:

image

复制第二行双引号里的内容(不带引号),粘贴到Lucky的Webhook的请求头中。

请求主体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"targets":[
{
"target":"url",
"constraint":{
"operator":"matches",
"value":"你的域名/*"
}
}
],
"actions":[
{
"id":"forwarding_url",
"value":{
"url":"http://#{ipAddr}/$1",
"status_code":301
}
}
],
"priority":1,
"status":"active"
}

其中targets​的value​填入的内容跟页面规则里的URL保持一致,该示例中为test.yourdomain.com/*
actions​ 中的url​为目标URL,通过lucky提供的参数构造,填为http://#{ipAddr}/$1​ ,接口调用成功包含的字符串:"success":true​。

​​image​​

这些全部完成后点击Webhook手动触发测试​,如果不成功,请检查各参数设置,配置正确后点击保存。

等待lucky执行成功webhook后,在浏览器中输入在页面规则中设定的域名,即可跳转至对应的公网地址。如需收藏或分享链接,只需将浏览器中的ip:port​ 部分重新替换回域名即可。

第六步 将反向代理服务器暴露至公网

经过之前的步骤已经可以方便地通过域名访问内网服务,但倘若有多个服务,则需配置多个页面规则和穿透隧道,然而Cloudflare免费计划只允许创建3个页面规则和10个重定向规则,部分地区的宽带甚至只能打通一个隧道。因此我们可以类比有公网地址的情况,通过反向代理服务,在只开启一个端口的情况下通过不同的二级域名来访问不同的内网服务。

基本思路

有公网IP时,我们只需通过DDNS将泛域名解析到宽带的公网IP,并将反向代理的端口暴露在公网下即可。
使用STUN穿透时,解析IP很容易,stun穿透得到的公网ip跟DDNS通过接口获取的ip一致,这部分操作可以照搬有公网的情况。
端口号不固定这点依旧利用重定向规则来实现跳转,目的是让最后跳转到前缀.顶级域名:端口号/...​的形式,再将所有二级域名指向公网ip。

首先搭建好反向代理,将泛域名通过DDNS解析到接口获取的ipv4地址,将stun穿透的目标地址和端口设置为反向代理的本地地址和https端口,并通过https://域名:公网端口​的形式进行访问,测试没问题之后再进行下一步。如果不配置证书,就把穿透目标端口设为反向代理的http端口,并通过http访问。

如果你还没有配置过DDNS、SSL续签、反向代理,请自行搜索配置方法,相关教程非常多。如果你只有简单的需求,那么可以考虑使用Lucky一站式解决。配置方式可参考winnas进阶篇之 lucky插件的使用也许是目前最通俗易懂的Lucky反向代理演示? ,注意协议要选择ipv4。

期望中的效果

我希望只是用一条规则,就能让泛域名*.bbb.com​匹配到的所有域名跳转到*.aaa.com​对应的子域名去。比如alist.bbb.com​->alist.aaa.com​、emby.bbb.com​->emby.aaa.com​。在此,我姑且将bbb.com​称为用于跳转的主域名,aaa.com​称为跳转后的域名。

这样当你想要访问域名前缀为sv1的服务,只需在浏览器中输入sv1.bbb.com/​,会自动跳转到https://sv1.aaa.com:[port]/​ ;如果要指定访问路径,输入sv1.bbb.com/path1/path2/path3​,会自动跳转到https://sv1.aaa.com:[port]/path1/path2/path3。

如果想要收藏或分享链接,只需把地址栏中的aaa改回bbb,并删除端口即可。

如果你想采用同一个域名下,二级域名向三级域名跳转的方案,如alist.example.com​->alist.4.example.com​,那么example.com​为主域名,4.example.com​为跳转后的域名。

配置重定向规则

首先确保你用于跳转的主域名下的泛域名(即*.bbb.com​)已解析到Cloudflare并开启小云朵;本地的反向代理使用跳转后的域名,且该域名下的泛域名(*.aaa.com​)已通过DDNS解析到解析到stun的公网ip。

接下来数出用于跳转的主域名有多少个字符,上文例子里bbb.com​就是7个字符,example.com​是11个字符,我们将其记作参数2​,给它加一个比你设置的域名前缀长度更大的数(比如10),可以得到参数1​。

参考第4、5步配置页面规则,webhook请求主体中的第一个expression改成"true"​(也就是所有传入连接都跳转),target_url的expression改成"concat(\"https://\",substring(http.host , -参数1,-参数2), \"跳转后域名:#{port}\", http.request.uri)"

如果你想通过http访问,请把target url的expression中的https改为http。

即现在的请求主体为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"id": "c7087a0fb757480xxxxxxxxxxxxxxxx",
"action": "redirect",
"expression": "true",
"description": "redirect",
"ref": "c7087a0fb757480xxxxxxxxxxxxxxxx",
"enabled": true,
"action_parameters": {
"from_value": {
"status_code": 307,
"target_url": {
"expression": "concat(\"https://\",substring(http.host , -17,-7), \"aaa.com:#{port}\", http.request.uri)"
},
"preserve_query_string": true
}
}
}

配置页面规则

页面规则即将被弃用,建议考虑替代方案。

如果你有两个域名,配置起来会容易一些,不容易出奇奇怪怪的问题,当然只有一个域名也不是不能用,根据情况任意选择一个方案即可。

如果你想通过http访问,请把actions中的https改为http。

1. 通过另一域名跳转

如果你有两个域名aaa.com​和bbb.com​,可以将bbb.com​解析到Cloudflare,添加*.bbb.com​ 的a记录并开启代理、添加页面规则。而本地的反向代理使用aaa.com,并将 *.aaa.com通过DDNS解析到stun的公网ip。
参考第4、5步配置页面规则,webhook请求主体中的target的url改成*.bbb.com/*​,actions的url改成https://$1.aaa.com:#{port}/$2

即现在的请求主体为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"targets":[
{
"target":"url",
"constraint":{
"operator":"matches",
"value":"*.bbb.com/*"
}
}
],
"actions":[
{
"id":"forwarding_url",
"value":{
"url":"https://$1.aaa.com:#{port}/$2",
"status_code":301
}
}
],
"priority":1,
"status":"active"
}

2. 仅使用一个域名跳转

毕竟好用的域名也是要花钱的,本着能省一点是一点的精神,一个域名也不是不能用,可以使用三级域名跳转。

首先将你配置了反向代理的域名(假设为example.com)托管至Cloudflare,将*.example.com通过DDNS解析到STUN穿透的公网IP(选择接口获取的ipv4地址)。

为了白嫖cf的证书,我们使用三级域名向二级域名跳转的方式:

添加一条*.a.example.com​的a记录(ip随便填)并开启代理,其中a​随便填。
将webhook请求主体中的target的url改为*.a.example.com/*​,actions的url改成https://$1.example.com:#{port}/$2​。即现在的请求主体为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"targets":[
{
"target":"url",
"constraint":{
"operator":"matches",
"value":"*.a.example.com/*"
}
}
],
"actions":[
{
"id":"forwarding_url",
"value":{
"url":"https://$1.example.com:#{port}/$2",
"status_code":301
}
}
],
"priority":1,
"status":"active"
}

还没完,由于Cloudflare的免费计划不允许上传证书,也不为三级域名提供免费证书,直接访问可能会出现关于SSL的奇奇怪怪的问题,建议直接删除Cloudflare的SSL证书。
回到Cloudflare,在SSL/TLS​ - 边缘证书​中删除证书并禁用SSL。由于我们只需要利用Cloudflare来跳转,并没有数据经过,所以大约是不会影响安全性。

请注意:该方式只能从http跳转,而多数现代浏览器默认使用https访问且不显示,因此在访问时务必确认地址栏的协议不是https。

结语

本文简单介绍了lucky的STUN穿透功能的使用方法,成功将反向代理服务暴露至公网。通过配置Cloudflare重定向规则实现了直接通过域名访问,近似达到了拥有公网IP的效果,同时满足了低延迟、大带宽、免费的需求,并且相对公益frp要稳定的多。

当然本期只实现了web应用的穿透,并未覆盖到大内网的全部痛点。下一期将利用STUN穿透解决大内网环境BT/PT上传下载难的问题,至于部分应用必须指定端口号的问题,我们放到第三期说。


Extra 使用经验

优化跳转速度

经过一段时间的使用,我发现双域名跳转的速度明显快于单域名跳转,有条件的情况下尽量使用双域名跳转,其中ClouDNS提供的免费域名不但可以挂到Cloudflare,而且可以使用API访问,功能甚至强于一众免费域名,缺点是Cloudflare无法接管DNS记录,需要手动续签证书,且不便于DDNS,因此只适合跳转和挂CDN。

支持301/302跳转的应用

  • Only Office的http api 支持跳转,但最好是从HTTPS到HTTPS,也就是需要双域名。
  • Mountainduck 支持挂载使用跳转的webdav,甚至支持从HTTP跳转至HTTPS,端口填80或443即可,但单域名跳转的延迟很高,每次请求应该都会单独跳转,建议使用双域名跳转。官网
  • 猫头鹰文件支持在移动平台上挂载使用跳转的webdav,安卓电视则可以使用NOVA Video Player