menu E4b9a6's blog
rss_feed
E4b9a6's blog
有善始者实繁,能克终者盖寡。

家庭宽带内网Nat打洞原理以及实践

作者:E4b9a6, 创建:2024-09-25, 字数:5838, 已阅:308, 最后更新:2024-09-25

现在很多个人宽带已经没有动态公网IP了,这使得在家庭内部署一些网络服务失去了意义

判断是否具备公网IP:

  1. 访问 http://cip.cc ,获得当前的公网 IP
  2. 查看路由器或者拨号软路由拨号后的 IP 是否相同,不同的话大概率是 Nat 网络
  3. 进一步验证,将路由器的页面管理端口修改至 9090 ,访问 http://public-ip:9090

如果访问不到,则说明处于 Nat 网络中,在 Nat 内网中部署的服务需要暴露出去的话,则需要进行 Nat 打洞

1. NAT网络

1.1. NAT

NAT 的全称是 Network Address Translation,即网络地址转换,指的是路由器等网络设备,在传输数据的过程中,改变数据中的 IP 地址的一种技术

NAT 技术示意图

随着全球联网设备越来越多,但 IPv4 地址资源有限,所以 NAT 技术在 IPV6 普及前都会是相当广泛的应用

1.2. NAT类型

NAT 分为好几种类型:

  1. Full Cone NAT:“完全圆锥形 NAT”,内部设备与某个外部服务建立tcp链接后(如示意图),在未关闭该tcp链路前,任何外部设备可以通过此链路端口访问到内部端口
  2. Restricted Cone NAT:“受限圆锥形 NAT”,在 Full Cone NAT 基础上,增加对访问链路的设备的 IP 限制,仅允许 tcp 链路的目标设备发送 tcp 数据包到内部设备
  • Port-Restricted Cone NAT:“端口受限圆锥形 NAT”,在 Restricted Cone NAT 的基础上,再增加端口检查,仅允许 tcp 链路的目标设备和目标端口发送tcp数据包到内部设备
  • Symmetric NAT:“对称 NAT”,与前三种不同,对称 NAT 会为每个新连接分配一个新的外部端口,即使是从同一个内部端口发起的连接

有许多服务依赖与网络通信,尤其是 P2P、游戏等,例如在Xbox、PlayStation上,可以检测对网络类型进行检测,结果分别如下:

  • open:拥有公网IP,最佳网络环境
  • moderate:对应前三种 NAT 类型,可用的网络环境
  • strict: 对称NAT,不可用的网络环境

1.3. 打洞

NAT 打洞技术是指通过一系列的技巧和协议,尝试在 NAT 上创建临时的映射或规则,使得两个设备可以在 NAT 网络后进行直接通信

Full Cone NAT 是最容易打洞的环境,而且对访问来源没有限制,可用于 NAS 文件分享、Web 服务等

而对于 Restricted Cone NATPort-Restricted Cone NAT 来说,打洞的效果则要打些折扣,打洞过程如下:

Restricted Cone NATPort-Restricted Cone NAT 的打洞流程复杂,且对客户端也有一定要求,但基本也满足游戏对战、P2P下载等服务

2. NAT 打洞

按照前面介绍的 NAT 类型,明确可以进行内网穿透的是 Full Cone NATRestricted Cone NATPort-Restricted Cone NAT三中

对称NAT 打洞成功率很低,Full Cone NAT 是最理想的打洞环境

2.1. 检测 NAT 类型

Windows:

Linux/MacOS:

Windows下载后运行即可,其他操作系统可以使用 pystun 来做检测,以 pystun 为例

在 debian12 下为例,首先安装 Python 的包管理器:

Bash
# 根据你的 python 版本来安装虚拟环境套件
sudo apt install python3.xx-venv

创建一个虚拟环境,然后安装 pystun:

Bash
python3.xx-venv -m venv venv
source venv/bin/active

安装 pystun :

Bash
pip install pystun

运行 pystun :

Bash
pystun3

下面是我的输出

Bash
(venv) ~$ pystun3
NAT Type: Full Cone
External IP: 12.125.45.119
External Port: 6622

可以看到我的 nat 类型是 Full Cone NAT

2.2. NAT 打洞实践

natmap 用于从ISP NAT公网地址到本地私有地址建立 TCP/UDP 端口映射

以 web 服务为例,运行一个 librespeed 测速服务为例,docker 运行如下:

Bash
sudo docker run \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=Etc/UTC \
  -e PASSWORD=chancel \
  -p 8080:80 \
  lscr.io/linuxserver/librespeed:latest

访问 http://localhost:8080 并测速,结果如下:

然后下载 natmap 并编译:

Bash
git clone --recursive https://github.com/heiher/natmap.git
cd natmap
make

# 可选
cp bin/natmap /usr/bin/natmap

运行 natmap 建立一个端口映射

Bash
/usr/bin/natmap -i ppp0 -s turn.cloudflare.com -h qq.com -t localhost -p 8080

参数解析:

  • -i:指定网卡,这里指定了宽带拨号产生的ppp0
  • -s:使用的外网 TURN 服务器
  • -h: 对于tcp而言,需要一个保持 tcp 长连接的对象,这里填了腾讯的服务器
  • -t/-p:指内网设备的IP和端口

运行输出如下:

Bash
/usr/bin/natmap -i ppp0 -s turn.cloudflare.com -h qq.com -t 127.0.0.1 -p 8080
12.125.45.119 4475 2001::223d:2c91:82b3 46701 tcp 14.213.121.3

第一个输出即公网IP,第二个输出则是公网端口,在其他网络下,访问 http://12.125.45.119:4475 后测速:

可以看到测速的结果有接近 50 Mbps,我的宽带是 100Mbps

2.3. 自动更新

从前面对 nat 技术的介绍不难看出,映射到公网IP和公网端口会随时间改变,所以可以结合 DDNS 等技术来实现长时间打洞效果

https://api.chancel.me/rest/api/v1/anyjson 接口为例实现长期打洞,该 API 使用如下:

Bash
$ curl -X POST https://api.chancel.me/rest/api/v1/anyjson?id=hello -H "Content-Type: application/json" -d '{"lihua": "hi!"}'
# 返回状态 1 ,表示存储成功
{"status":1,"msg":"Data stored successfully","data":null,"version":"V1.0.0"}

$ curl -X GET https://api.chancel.me/rest/api/v1/anyjson?id=hello
{"lihua":"hi!"}

这个接口可用于临时存储 json,所以写一个 bash 脚本 natmap.sh 如下:

Bash
#!/bin/sh

/usr/bin/curl -X POST https://api.chancel.me/rest/api/v1/anyjson?id=secret -H "Content-Type: application/json" -d "{\"ip\": \"${1}\",\"port\":\"${2}\"}"

其中 $1$2 是由 natmap 传入的,即公网IP和公网端口,这样 natmap 会在打洞成功后执行该脚本将打洞结果上传到接口中

运行试试:

Bash
chmod +x natmap.sh
/usr/bin/natmap -i ppp0 -s turn.cloudflare.com -h qq.com -t 127.0.0.1 -p 8080 -e natmap.sh

此时在其他网络中,使用curl来获取打洞结果

Bash
curl https://api.chancel.me/rest/api/v1/anyjson\?id\=secret
{"ip":"12.125.45.119","port":"4467"}

可以看到,成功获取到了打洞结果,在其他网络中就可以利用这个结果来访问对应服务

以上为示例,可以灵活结合 DDNS 技术来实现各种内网服务穿透

最后,结合 supervisor 来实现 natmap 后台自动运行:

INI
[program:natmap]
command=/opt/natmap/natmap -i ppp0 -s turn.cloudflare.com -h qq.com -t 127.0.0.1 -p 22 -e /opt/natmap/natmap.sh
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
stdout_logfile_maxbytes=32MB
user=app

3. 对称NAT

2024年后,广东大部分地区的宽带都变成了 Symmetric NAT,这非常糟糕

在光猫拨号情况下,即使是 Full Cone NAT 也会被识别为 Symmetric NAT

所以需要将光猫更改为桥接,拨号由路由器/软路由来拨号,再结合DMZ来做网络类型测试

光猫更改为桥接,这一点需要光猫的管理员账户,可以通过

  • 找装宽带运维的师傅要
  • 淘宝找店铺解决

[[replyMessage== null?"发表评论":"发表评论 @ " + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageResponse.total]])

还没有可以显示的留言...
gravatar
[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[getEnviron(messageItem.m_environ)]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[getEnviron(messageItem.m_environ)]]