宽带私网IPv4开TCP服务,任意主机无客户端直连访问

有消息称国内各大运营商将会逐步收紧家庭宽带分配公网IPv4地址,有些地区已经在执行了,申请改公网IPv4的难度越来越大了。

好消息是几大运营商的固网宽带的CGNAT基本都是NAT1(Full cone)类型的,技术层面上给打洞开TCP服务允许任意公网主机访问提供了可能性。在此也非常感谢v2ex网友mike wang分享了这一思路。

现在,我们可以很方便的借助 natmap 工具来完成私网内的TCP服务端口向公网开放。

1. 适用于路由器拨号的场景,如果是光猫拨号,需要登录光猫配置DMZ。(也有用户反馈光猫实为NAT1无需配置DMZ)
2. 建议将natmap程序运行在拨号路由器上,如果运行在用户侧内网PC上,则同样需要配置拨号路由器的DMZ。
3. 支持调用脚本进行公网地址分发,比如更新到DDNS记录中。

下面以OpenWrt 路由器拨号并将natmap运行在路由器上例举两种基本场景的使用方法,详细使用文档见项目的README。

项目地址: https://github.com/heiher/natmap
程序下载: https://github.com/heiher/natmap/releases

场景一:开放路由器HTTP服务至公网

1. 配置防火墙

配置防火墙允许WAN口访问路由器的TCP 80端口:
登录路由器Web管理页面,转到 网络 -> 防火墙 -> 传输规则,增加如下配置:

* Protocol: TCP
* Source zone: wan
* Destination zone: Device (input)
* Destination port: [bind port]
* Action: accept

2. 运行 natmap

natmap -s stun.stunprotocol.org -h qq.com -b 80

程序运行后会输出映射后的公网IPv4地址和端口,在浏览器中能够访问http://101.82.24.32:43648说明映射成功。

101.82.24.32 43648 2001::aa80:6552:1820 80 tcp

场景二:开放内网PC上SSH服务至公网

1. 运行 natmap

natmap -s stun.stunprotocol.org -h qq.com -b 80 -t 10.0.0.2 -p 22

转发的目标内网主机地址 10.0.0.2,目标端口 22。

场景三:开放内网PC上的WireGuard服务至公网

1. 运行natmap

natmap -u -s stun.qq.com -b 6900 -t 192.168.0.2 -p 6901 -e /usr/bin/ddns

192.168.0.2: 运行WireGuard的内网主机
6901: 运行WireGuard的内网主机端口
/usr/bin/ddns: 将脚本参数3(IP4P)更新到DDNS域名的AAAA记录中。

2. WireGuard Android客户端访问

下载支持IP4P地址格式的WireGuard Android客户端: https://github.com/heiher/wireguard-android/releases/
Peer地址写为:

domain:0

Run OpenWrt 22.03 in systemd-nspawn container

Issue

The dnsmasq can’t start to running.

Why? The ujail is enabled by default on OpenWrt 22.03, and no privilege to do some jail operation in systemd-nspawn container. e.g. mount /tmp/xxx to /dev/log

How to fix

0x1. Uninstall procd-ujail and procd-seccomp

opkg remove procd-ujail
opkg remove procd-seccomp

0x2. Fix dnsmasq service script

/etc/init.d/dnsmasq:

[ -x /sbin/ujail -a -e /etc/capabilities/ntpd.json ] && {
	procd_add_jail dnsmasq ubus log
	procd_add_jail_mount $CONFIGFILE $DHCPBOGUSHOSTNAMEFILE $DHCPSCRIPT $DHCPSCRIPT_DEPENDS
	procd_add_jail_mount $EXTRA_MOUNT $RFC6761FILE $TRUSTANCHORSFILE
	procd_add_jail_mount $dnsmasqconffile $dnsmasqconfdir $resolvdir $user_dhcpscript
	procd_add_jail_mount /etc/passwd /etc/group /etc/TZ /etc/hosts /etc/ethers
	procd_add_jail_mount_rw /var/run/dnsmasq/ $leasefile
	case "$logfacility" in */*)
		[ ! -e "$logfacility" ] && touch "$logfacility"
		procd_add_jail_mount_rw "$logfacility"
	esac
}

Linux socket bind IPv6 only

Socket options

IPv6 support some protocol-specific socket options that can be set with setsockopt and read with getsockopt. The socket option level for IPv6 is IPPROTO_IPV6. A boolean integer flag is zero with it is false, otherwise true.

IPV6_V6ONLY

If this flag is set to true (nonzero), then the socket is restricted to sending and receiving IPv6 packets only. In this case, an IPv4 and an IPv6 application can bind to a single port at the same time.

If this flag is set to false (zero), then the socket can be used to send and receive packets to and from an IPv6 address or an IPv4-mapped IPv6 address.

The argument is a pointer to a boolean value in an integer.

The default value for this flag is defined by the contents of the file /proc/sys/net/ipv6/bindv6only. The default value for that file is 0 (false).

Example

int one = 1;
setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof (one));

Refer to: https://man7.org/linux/man-pages/man7/ipv6.7.html

IP addr-label persistent by systemd

This is an example shows how to make IP address label persistent by systemd-networkd.

/etc/systemd/network/eth0.network :

[Match]
Name=eth0

[Network]
Address=192.168.0.1/24
Gateway=192.168.0.254
DNS=192.168.0.254

[IPv6AddressLabel]
Label=100
Prefix=2409::/16

[IPv6AddressLabel]
Label=100
Prefix=2606::/16

It’s successful if you can see labels that you configured :

$ ip addrl
prefix ::1/128 label 0 
prefix ::/96 label 3 
prefix ::ffff:0.0.0.0/96 label 4 
prefix 2001::/32 label 6 
prefix 2001:10::/28 label 7 
prefix 2606::/16 dev br0 label 100 
prefix 2409::/16 dev br0 label 100 
prefix 3ffe::/16 label 12 
prefix 2002::/16 label 2 
prefix fec0::/10 label 11 
prefix fc00::/7 label 5 
prefix ::/0 label 1