0%

NatMap IPv4直连方案

本文仅作为技术讨论及分享,严禁用于任何非法用途。

前言

目前申请公网ip越来越难,虽然目前IPv6越来越普及,仍有部分场景不能覆盖到,如部分企业内网、公共WiFi等,如何高效远程接入家庭内网,本文将分享由 NatMap+WireGuard远程接入家庭内网的方案。

本方案的优势在于,不同于FRP的P2P连接需要依赖客户端的支持(如IOS无法使用),在没有公网IPv4下可以直接使用IPv4直连内网。本方案是笔者自认为的最优方案,不一定是读者的最优方案,建议阅读后自行选择。

本方案作为IPv6接入的补充,可以与IPv6同时使用,建议优先使用IPv6。

环境要求

  1. 网络类型为NAT1
  2. 内网24小时运行的服务器,如openwrt、pve等
  3. 配置好的WireGuard服务
  4. Server酱注册/PushDeer

正文

NAT1网络

NAT1网络有很多好处,比如便于建立P2P连接,有利于BT、PT下载、FRP穿透等,家庭网络建议开启。至于其安全性,作为安全从业者,经过查阅资料,个人认为在没有公网IP的情况下是安全的,至于为什么长篇大论就不说了,可以自己研究。

怎么开启NAT1,这个没啥好说的,光猫桥接模式+路由器选择NAT1即可,这个根据自己的设备搜一搜就有很多文章。

可以使用NatTypeTester测试自己的NAT类型。
https://github.com/HMBSbige/NatTypeTester

NatMap

NATMap实现与拥有动态公网IP同等的访问效果,它通过打通完全锥型(Full cone)类型NAT的TCP、UDP端口,实现访问侧任意主机在无需客户端的情况下进行直连访问。

https://github.com/heiher/natmap

再使用前可以先阅读官方文档,理解其原理,本文更偏向于实践。与作者方案的不同之处是,笔者并不使用作者的IP4P方法,因为笔者手机是IOS,作者的提供WireGuard分支只支持安卓,也不支持Windows。

NatMap成功后会返回公网IP和端口,所以笔者的方案是通过server酱把成功后的IP和端口发送到微信,然后在客户端手动修改WireGuard配置,没有什么高深技术,只是记录配置过程,所以大神可以省流。

开始

首先配置好WireGuard服务,如果还没配置好请搜索相关教程,可以看看司博图的,配置起来非常简单,不再赘述。

其实作者也写了文档,可以看作者的。

然后是调试natmap,通过下面命令启动natmap,当然不使用su命令也行,个人习惯,能不给root就不给。其中-p是WireGuard的端口,因为跟WireGuard是同一台服务器,所以-t是127.0.0.1,如果不是可以写其它内网地址。

1
su nobody -m -p -c "./natmap-linux-x86_64 -u -s turn.cloudflare.com -t 127.0.0.1 -p 5xxx"

运行后一段时间,成功的话有类似以下的输出,第一个的你的公网出口IP,第二个是映射的端口,我们主要关注这两个。第三个是作者提出的IP4P地址,可参考作者文档。

此时使用手机或者其它设备通过WireGuard连接,可以连接成功。

注册Server酱或者PushDeer,这里推荐PushDeer,实测Server酱免费用户只能每天发5条,PushDeer则无限制。

Server酱:
https://sct.ftqq.com/
PushDeer:
https://github.com/easychen/pushdeer

编写通知脚本

作者提供了-e参数供用户调用脚本进行ddns更新,但有个坑点是只支持调用sh脚本,这方面缺少文档。

先创建文件存放目录及文件,注意不要放在/root目录和/home目录,大概率出现权限问题,因为后面自启动是通过nobody用户运行的,权限很低。

1
2
3
4
5
mkdir /tools
cd /tools

touch ddns.sh
touch natmap-ddns.py

ddns.sh内容如下:

1
2
#!/bin/sh
/usr/bin/python3.9 /tools/natmap-ddns.py $*

natmap-ddns.py内容如下,根据自己的通知方式修改apikey或者pushkey:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/python3.9

import sys
import urllib.request
import urllib.parse

# debug su nobody -m -p -c "./natmap-linux-x86_64 -u -s turn.cloudflare.com -t 127.0.0.1 -p 5xxx -e \"/tools/natmap-ddns.py\""
ip4 = sys.argv[1]
port = sys.argv[2]
ip4p = sys.argv[3]

localport = sys.argv[4]
ttype = sys.argv[5]
localip = sys.argv[6]


pubaddr = f'{ip4}:{port}'


msg = f'[+] Public: {pubaddr} Local: {localip}:{localport} type: {ttype}'
print(msg)

# send to serverChan
def send_to_serverchan(pubaddr):
apikey = 'xxx'
url = f'https://sctapi.ftqq.com/{apikey}.send?title=NatMap&desp={pubaddr}'
try:
r = urllib.request.urlopen(url)
print('[+] Send: Server酱消息发送成功!')
except:
print('[!] Error: Server酱消息发送错误!')


def send_to_pushdeer(msg):
# https://api2.pushdeer.com/message/push?pushkey=key&text=要发送的内容
pushkey = 'xxx'
url = f'https://api2.pushdeer.com/message/push?pushkey={pushkey}&text={msg}'
try:
r = urllib.request.urlopen(url)
print('[+] Send: PushDeer消息发送成功!')
except:
print('[!] Error: PushDeer消息发送错误!')

msg = urllib.parse.quote(msg)
send_to_pushdeer(msg)

配置自启动服务

通过systemd启动,适用于大部分的Linux系统,如Ubuntu、CentOS等。

1
touch /etc/systemd/system/natmap.service

natmap.service内容如下,其中ExecStart修改为自己调试好的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=NatMap Service
Documentation=NatMap Service
After=network.target nss-lookup.target

[Service]
User=nobody
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/tools/natmap-linux-x86_64 -u -s turn.cloudflare.com -t 127.0.0.1 -p 5xxxx -e /tools/ddns.sh
Restart=on-failure
RestartPreventExitStatus=23

[Install]
WantedBy=multi-user.target

设置natmap服务自启动,并启动服务

1
2
3
systemctl daemon-reload
systemctl enable natmap.service
systemctl start natmap.service

然后微信(Server酱)或者PushDeer就收到IP地址啦,配置到WireGuard连接即可。

最后

本方案或许还不够优雅,也是无奈之举,希望有更多支持IP4P格式的应用,如Windows的WireGuard支持,那么就可以实现自动化的WireGuard连接了,当然,或许可以考虑tailscale。至于笔者为什么不直接使用tailscale?可以说是洁癖吧,不喜欢自己的内网通过公网的服务商进行接入,headscale配置又很复杂,感觉没啥必要,毕竟平时都是同IPv6连接,额外增加那么多资源去配置headscale似乎很不优雅,natmap正好补充了这方面的不足。