打通大内网第三期 固定服务端口 (基于Lucky的STUN穿透)

前言

本教程不适合小白,本人因为水平有限,也无法解决大家遇到的问题,一旦出现问题还得请各位自行摸索。
首要条件:NAT 类型为 NAT1,即 fullcone;光猫桥接或开启 dmz;路由器能刷机或者开启 upnp/dmz
建议:路由器能够控制具体的端口开放与否,而不是只能全开或全关。
本文中的操作基于 OpenWrt 上的 Lucky,在其它设备使用或是使用 NATMAP 进行穿透同样可以类比。建议在阅读了第一期与第二期的基础上阅读本文。

需解决的问题与思路

STUN穿透的端口不固定,并且更换较为频繁,虽然在第一期中,我们使用Cloudflare页面规则,将域名重定向到了公网端口,解决了web应用的访问;而绝大多数需要连接自建后端的应用程序并不支持重定向,必须指定端口,甚至部分应用程序不允许修改默认端口。

我们可以借鉴其它内网穿透软件的思路,通过一个端口来转发其他端口的流量,常见的做法有两种:

  • 通过frp的stcp和sudp,将内网端口一对一的绑定到异地设备的本机端口上。
  • 通过wireguard或n2n,将内网整个网段转发过去。

因为我的内网服务比较多,不想让连teamspeak的朋友都来欣赏我的“学习资料”,所以我选择使用frp。

此处放两个教程供各位参考,不过他们用的是NATMAP。

使用NATMap在NAT-1私网IP宽带上部署FRP服务
natmap内网穿透让内网实现公网IP,远程访问异地组网,做frps,ssh ,web,wireguard..

如果你不考虑访问者的权限,仅仅想把整个内网网段转发出去,可以考虑使用tailscale,并利用Lucky将搭建的Derp中继服务器穿透出去,操作更为简单,具体方法请参考第四期教程。

第一步 在内网安装frps和frpc,并配置stcp

在内网配置的frps和frpc使用官方版即可,此处简单带过。官方文档

frps

在路由器或nas上下载并安装frps,我是在Unraid里用docker安装:

配置frps.ini :

[common]
bind_port = 7000
token = your_token

只需配置bind_porttoken就行,也就是frps的访问端口和令牌,默认采用TCP协议。

配置完成后运行frps,接下来参考之前的教程,它创建STUN穿透通道、配置端口转发、开启防火墙,在外网能ping通后为它设置开机自启。

frpc

为何在内网中也要frpc?这跟stcp与sudp的工作方式有关,官网的描述如下:

STCP 和 SUDP 的 (S) 的含义是 Secret。其作用是为 TCP 和 UDP 类型的服务提供一种安全访问的能力,避免让端口直接暴露在公网上导致任何人都能访问到。

这两种代理要求在被访问服务的机器上以及要访问的用户的机器上都部署上 frp 的客户端。被访问的服务所在机器叫做服务端,另一端叫做访问端。

frp 会在访问端监听一个端口和服务端的端口做映射。访问端的用户需要提供相同的密钥才能连接成功,从而保证安全性。

参考官方的使用示例 ,配置时根据需要把~~xtcp~~改成~~stcp~~~~sdup~~即可。 官方已经不再推荐使用ini配置文件。

下载安装官方版frpc后,编辑frpc.ini。common中的token必须与frps相同。

[common]
server_addr = 127.0.0.1
server_port = 7000
token = your_token

# jellyfin
[svc_jellyfin]
type = stcp
sk = B9jNFfUy3hDrmi
local_ip = 127.0.0.1
local_port = 8096
use_compression = true
role = server

# nginx
[svc_nginx]
type = stcp
sk = x5Y6f8VBbXQ5NY
local_ip = 192.168.0.115
local_port = 18443
use_compression = true

# teamspeak
[svc_ts1]
type = sudp
sk = khgtqgyCk3u3JZ
local_ip = 127.0.0.1
local_port = 9987
use_compression = false
[svc_ts2]
type = stcp
sk = khgtqgyCk3u3JZ
local_ip = 127.0.0.1
local_port = 10011
use_compression = true
[svc_ts3]
type = stcp
sk = khgtqgyCk3u3JZ
local_ip = 127.0.0.1
local_port = 30033
use_compression = true

我将frpc和frps部署在了同一台机器上,因此server_addr填的是127.0.0.1,你要改为frps的内网ip。有网友反映将frps和frpc部署在同一台机器上可能会出错,但是我没遇到。

为你想要在外网访问的所有端口都配置frpc的stcp/sudp服务端(如果不指定role则默认为server),local_iplocal_port为相应服务的内网地址和端口。我这里的添加了本地地址上的jellyfin、nginx和teamspeak。

如果你需要访问的所有服务都经过反向代理,那也可以只为反向代理配置stcp,并在异地机器上修改HOSTS文件。

第二步 解析frps的公网端口

绕了这么一大圈,还是要面对最头疼的问题:frps的公网访问端口不固定怎么办?

解决思路

ip4p

NATMAP的作者给出了名为ip4p的方案,也就是将ipv4地址和端口转化成ipv6地址,添加为一条AAAA记录。当公网地址或端口改变时,只要修改该记录,即可实现类似DDNS的效果。作者也对frpc进行了修改,为它添加了解析ip4p的功能。支持ip4p的frp

The IP4P address format uses IPv6 special addresses to encode IPv4 addresses and ports for easy distribution through DNS AAAA records.

2001::{port}:{ipv4-hi16}:{ipv4-lo16}
  • ip4p存在的问题

在windows平台和安卓平台上,如果没有外网ipv6环境,那么他程序里使用的LookupIP函数不会获取AAAA记录。虽然调用nslookup可以解析AAAA记录,但是我懒......

也就是说,你连不上外网ipv6,就解析不了ip4p,但是如果你连得上ipv6,为什么不用ipv6直连呢......

TXT记录

我进交流群向带佬求助后,带佬建议我直接把公网ip和端口放进TXT记录里。于是我花了俩小时自学了一下golang,添加了解析TXT记录的功能。我的做法是,假设公网地址是183.6.66.666:6666,将其用BASE64编码,变成MTgzLjYuNjYuNjY2OjY2NjY=放进TXT记录里,供frpc解析。如果你觉得不安全,那可以考虑加个AES加密之类的。

创建TXT记录

进入Cloudflare后台,在dns - 记录中创建一条TXT记录,TTL我设为了一分钟,不确定是否生效。

将你刚才创建的TXT记录的域名、Cloudflare的邮箱、Global API Key和域名的区域id填入下面的指令里(忘记的可以看看第一期第四步和第五步),并在命令行里执行。

curl -s -X GET "https://api.cloudflare.com/client/v4/zones/区域id/dns_records?type=TXT&name=ip4p.s***********n.online" -H "X-Auth-Email:******@qq.com" -H "X-Auth-Key:f9***********************1b" -H "Content-Type: application/json"

把结果里"id":后面的那一串数字+字母复制下来,这就是这条记录的id。

把包括刚才id在内的信息替换到下一行指令里执行,尝试对刚才添加的TXT记录进行修改:

curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/区域id/dns_records/记录id" -H "X-Auth-Email:******@qq.com" -H "X-Auth-Key:f9*************************1b" -H "Content-Type: application/json" --data '{"type":"TXT","name":"ip4p.s************.online","content":"changed","ttl":60,"proxied":false}'

再回到Cloudflare后台,应该就能看到这条记录更改了。

部分免费域名无法使用API修改。


配置脚本

在以下脚本中填入Cloudflare的邮箱、Global API key、区域id、txt记录的id。

domain="ip4p.s******n.online"
zone="e7***************************fb"
txt_id="a19***********************8a06"
email="****@qq.com"
key="f9*****************1b"

echo "change txt record"
#base64 encode
addr_64=$(echo -n ${ipAddr} | base64)
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone/dns_records/$txt_id" \
-H "X-Auth-Email: $email" -H "X-Auth-Key: $key" \
-H "Content-Type: application/json" \
--data '{"type":"TXT","name":"'$domain'","content":"'$addr_64'","ttl":60,"proxied":false}'

然后编辑穿透规则,开启自定义脚本触发,把它粘贴到自定义脚本中。

第二步剩余内容已经过时,仅留作记录,请跳至第三步


配置脚本

Lucky从2.5.1起,支持STUN地址改变时调用脚本,本节内容失效。

在第二期创建的webhook服务器的脚本目录下创建frp.sh,并填入Cloudflare的邮箱、Global API key、区域id、txt记录的id。我的ip4p.s********n.online域名除了txt记录外,还添加了ip4p的aaaa记录;如果你需要,请仿照上一步手动添加一条aaaa记录,并填入这条记录的id,如果不需要那就把对应的部分注释掉。

#!/bin/bash
# param is ip,port,addr

domain="ip4p.s********n.online"
zone="e7***************************************fb"
ip4p_id="96************************************1b93"
txt_id="a19*********************************8a06"
email="******@qq.com"
key="f9*************************1b"

echo "change ip4p record"
array=(${ip//./ })
ip4p=2001::$(printf '%x:%x%x:%x%x\n' $port ${array[0]} ${array[1]} ${array[2]} ${array[3]})
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone/dns_records/$ip4p_id" \
-H "X-Auth-Email:$email" -H "X-Auth-Key:$key" \
-H "Content-Type: application/json" \
--data '{"type":"AAAA","name":"'$domain'","content":"'$ip4p'","ttl":60,"proxied":false}'

echo ""
echo "change txt record"
# base64 encode
addr_64=$(echo -n $addr | base64)
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone/dns_records/$txt_id" \
-H "X-Auth-Email:$email" -H "X-Auth-Key:$key" \
-H "Content-Type: application/json" \
--data '{"type":"TXT","name":"'$domain'","content":"'$addr_64'","ttl":60,"proxied":false}'

在Lucky中设置webhook

Lucky从2.5.1起,支持STUN地址改变时调用脚本,本节内容失效。

打开Lucky后台,编辑stun的穿透规则,打开webhook,将接口地址设置为http://webhookd的ip地址:wenhookd的端口/frp?ip=#{ip}&port=#{port}&addr=#{ipAddr}(webhook服务器地址、端口和脚本名称根据实际情况来),接口调用成功包含的字符串填"success":true

第三步 在异地电脑上配置frpc

异地电脑上的frpc必须使用魔改版,如需解析txt记录,可以使用我改的版本,但我只在Windows上编译过,其它平台请自行编译。

配置frpc.ini

修改frpc.iniserver_addr填添加了ip4p或TXT记录的域名,server_port不用填,建议指定dns_server

[common]
server_addr = ip4p.s******n.online
token = your_token
dns_server = 8.8.8.8

# jellyfin
[vst_jellyfin]
type = stcp
role = visitor
server_name = svc_jellyfin
sk = B9jNFfUy3hDrmi
bind_ip = 127.0.0.1
bind_port = 38096
use_compression = true

# nginx
[vst_nginx]
type = stcp
role = visitor
server_name = svc_nginx
sk = x5Y6f8VBbXQ5NY
bind_ip = 127.0.0.1
bind_port = 18443
use_compression = true

# teamspeak
[vst_ts1]
type = sudp
role = visitor
server_name = svc_ts1
sk = khgtqgyCk3u3JZ
bind_ip = 127.0.0.1
bind_port = 9987
use_compression = false
[vst_ts2]
type = stcp
role = visitor
server_name = svc_ts2
sk = khgtqgyCk3u3JZ
bind_ip = 127.0.0.1
bind_port = 10011
use_compression = true
[vst_ts3]
type = stcp
role = visitor
server_name = svc_ts3
sk = khgtqgyCk3u3JZ
bind_ip = 127.0.0.1
bind_port = 30033
use_compression = true

访问端必须手动指定rolevisitor,必须指定连接的服务端名称server_name,并且sk应与服务端相同。

如果bind_ip填为127.0.0.1,则仅有本设备能访问。若想让其它设备访问,则填为0.0.0.0

在Windows上将frpc注册为服务

这一步面向零基础使用者。(也就是你的小白朋友)

先尝试运行一次

打开frpc.exe所在文件夹,在资源管理器空白处右键 - 打开命令提示符

输入.\frpc.exe并按下回车。如果没有出现错误,可以尝试访问所有的服务。

如果发现有的服务访问不上,打开frpc.ini,找那个服务到对应的bind_port,记下这个数字。

创建文件检查端口是否占用.ps1,代码如下。右键 - 使用PowerShell运行

while(1)  
{  
    $port = Read-Host "请输入需要查询的端口,输入0退出:"
    IF ($port -eq 0) {exit}
    $res=$(Get-NetTCPConnection | ? {$_.LocalPort -eq $port})
    if ($res){
        echo $res
        Get-Process -Id (Get-NetTCPConnection -LocalPort $port).OwningProcess
    }
    else {echo "未被占用"}
}

你可以输入刚才记下的数字,查看该端口是否被占用、被什么程序占用,然后选择关闭该程序或者选择另一个没有被占用的端口(1025-65535之间)。

一旦关闭命令提示符窗口,frpc就会关闭。

注册服务

可以使用nssm将程序注册为服务。

先将frpc关闭。在资源管理器中打开nssm.exe所在目录。32位系统打开win32目录,64位系统打开win64目录。

在资源管理器空白处右键 - 打开命令提示符,输入.\nssm.exe install frpc

会弹出NSSM service installer窗口,Application Path选择frpc.exeStartup directory选择frpc.exe所在目录,Arugments不填,点击Install service。

任务管理器 - 服务 - 打开服务弹出窗口中找到frpc。右键frpc,点击属性,将启动类型改为自动,并点击确定。再右键frpc,点击启动

魔改版frp使用问题总结

如果你的应用强制要求https连接(如BitWarden),可以在frp中添加反向代理的端口,并在异地电脑上修改hosts劫持域名。

我尚未解决的问题记录如下:

  • 我尝试让frp使用QUIC协议,访问端可以连接到服务端,可以ping通,但是几乎收不到包;
  • 部分常用第三方远程桌面无法正常使用(moonlight可以)
    • RustDesk的ID服务器可以连接,但是中继服务器无法连接;
    • parsec无法打洞(不过我也不觉得能成功)。
  • 安卓手机若无root,开了穿透软件就无法魔法上网。

结语

至此,我们补上了STUN内网穿透的最后一块拼图,基本解决了大内网环境下的所有痛点,近似达到了拥有公网ip的效果。但使用体验仍不能称得上完美,期待之后能有更好的方案出现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容