前些日子,因之前使用的 TP-LINK TL-R5408M 没有 ipv6 防火墙相关的能力,且对 OpenWrt 逐渐熟悉起来,决定使用 OpenWrt 作为主路由。但在安装 luci-app-upnp 并启用后,原路由中的一些常用的转发,一个都没生效,便开始找寻原因。

网络环境为:光猫改了桥接模式,OpenWrt 拨号,获取到的是非公网 IP,为运营商大内网 IP。

系统版本:ImmortalWrt 23.05.4,upnp服务端版本:miniupnpd-nftables 2.3.3-2。

排查思路

查看日志

Mon Dec 29 16:46:16 2024 daemon.notice miniupnpd[13276]: HTTP listening on port 5000
Mon Dec 29 16:46:16 2024 daemon.notice miniupnpd[13276]: Listening for NAT-PMP/PCP traffic on port 5351

服务正常启动,端口有在监听。

Mon Dec 29 16:47:01 2024 daemon.notice miniupnpd[13276]: private/reserved address 100.64.xxx.xxx is not suitable for external IP

但客户端发起相关请求时,会出现类似的日志。

搜索到其他网友也有类似的问题:

其中提到,大概有两个原因。

  • 打开了 ipv6,会优先使用 ipv6,解决方法为可在 upnp 配置中禁用 ipv6。
  • 新版本的 miniupnpd 会对外网 ip 为非公网 ip 停用转发能力。 GitHub上的回答

解决方案

upnpd 的配置文件在 /etc/config/upnpd,在我使用的 luci-app-upnp 页面上,没有 ipv6 相关配置。直接查看配置,发现 option ipv6_disable '1' 说明 ipv6 相关已被禁用。而当前 wan 口获取的 ip 地址正好在内部地址的名单中。

针对这种情况,大概有两种解决方案:

  1. 使用旧版本的包,没有做相关公网 IP 的监测。或想办法让 upnp 监测到的是公网 IP。
  2. 使用 STUN。

因对 STUN 不太了解,后面有时间再研究,先看看第一种解决方法。

UPnP工作不正常,“活动的 UPnP 重定向”里面不显示任何东西 · immortalwrt immortalwrt · Discussion #861 提到了一个相对复杂的解决方案,在wan网卡启动时执行脚本,从myip.ipip.net 获取公网 IP 后,设置 upnpd 的 external_ip。但同时提到,external_ip 设置为任意公网 IP 也可以。但又同时说到,只是一个心理安慰,实际没有什么作用。

尝试了上述方法,经过设置后,在 luci 上就能看到 有活动的 UPnP 重定向了。

  • 编辑 /etc/config/upnpd 添加配置option external_ip '8.8.8.8' 最终的配置
  • 重启服务 /etc/init.d/miniupnpd restart
  • 重启客户端,如迅雷

真的解决了吗

  • 这种大内网环境下 external_ip 应该是什么?实际的公网 IP ?运营商的内网 IP?还是任意公网 IP 都可?
  • 而更大的问题是,真的有必要非公网情况下配置 UPnP,即使真正配置了,它有用吗?UPnP 到底用来解决哪一类问题?

external_ip 应该是什么

简单粗暴的方法,看看国内的硬路由是怎么处理。

使用的是「中兴(ZTE)晴天墙面路由器AX3000」,同样适用路由器拨号,获取到的是运营商内网 IP。开启 UPnP,同时把防火墙等级都设置为低(相当于关掉)。

电脑连接,用 ChatGPT 生成了一个代码,用于监测。

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/NebulousLabs/go-upnp"
)

func main() {
	// 发现 UPnP 设备(路由器)
	device, err := upnp.Discover()
	if err != nil {
		log.Fatalf("无法发现 UPnP 设备:%v", err)
	}
	fmt.Println("发现 UPnP 设备:", device.Location)

	// 获取外部 IP 地址
	externalIP, err := device.ExternalIP()
	if err != nil {
		log.Fatalf("无法获取外部 IP 地址:%v", err)
	}
	fmt.Println("外部 IP 地址:", externalIP)

	// 要映射的端口
	port := uint16(50248)

	// 通过 UPnP 映射端口
	err = device.Forward(port, "Go UPnP HTTP Server")
	if err != nil {
		log.Fatalf("无法映射端口:%v", err)
	}
	// 在程序退出时取消端口映射
	defer device.Clear(port)
	fmt.Printf("端口 %d 已通过 UPnP 映射\n", port)

	// 设置 HTTP 处理函数
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "hello world")
	})

	// 启动 HTTP 服务器
	localAddr := fmt.Sprintf(":%d", port)
	log.Printf("HTTP 服务器正在端口 %d 上运行", port)
	err = http.ListenAndServe(localAddr, nil)
	if err != nil {
		log.Fatalf("无法启动 HTTP 服务器:%v", err)
	}
}

运行结果为

发现 UPnP 设备: 0x104e7d500
外部 IP 地址: 100.64.xxx.xxx
端口 50248 已通过 UPnP 映射
2024/12/29 15:53:22 HTTP 服务器正在端口 50248 上运行

这意味着,拿到的是运营商内网 IP。

在 UPnP 协议中,external_ip 是指路由器或网关设备对外的公网 IP 地址。 external_ip 的作用

  1. 外网通信的入口。路由器的 external_ip 是所有内网设备与外网通信时使用的 IP 地址。对外暴露的服务(如游戏服务器、Web 服务器)会通过此 IP 地址被外网访问。 …

在我理解的 UPnP 使用场景中,它的作用类似于自动配置路由器端口转发。那么,这个对外宣告的 external_ip 自然也应该为运营商内网 IP。那么在 OpenWRT下,真正要与国内硬路由效果做到一致,只能使用老版本的包。或自行删除有关内网 IP 段监测的代码,重新编译。

之前忽略的 STUN 呢?miniupnpd 项目代码注释上,说明了这个情况。miniupnp/miniupnpd/miniupnpd.conf at miniupnpd_2_3_7 · miniupnp/miniupnp

WAN 接口必须具有公网 IP 地址,否则它会位于 NAT 后面,无法进行端口转发。在某些情况下,WAN 接口可能位于不受限制的全锥形 NAT(1:1)之后,此时所有传入流量都会被 NAT 并路由到 WAN 接口而不进行任何过滤。在这种情况下,miniupnpd 需要知道公网 IP 地址,可以通过使用 STUN 协议向外部服务器请求来获取该地址。以下选项启用从 STUN 服务器检索外部公网 IP 地址并检测 NAT 类型。您还需要在下面的 stun_host 选项中指定外部 STUN 服务器。

尝试配置这个 STUN 服务 Stuntman - open source STUN server

A STUN service hosting the Stuntman code is being run at stunserver2024.stunprotocol.org (UDP and TCP ports 3478).

查看日志:

Tue Dec 31 00:16:42 2024 daemon.warn miniupnpd[5433]: STUN: ext interface pppoe-wan with private IP address 100.64.185.248 is now behind restrictive or symmetric NAT with public IP address 171.221.137.180 which does not support port forwarding
Tue Dec 31 00:16:42 2024 daemon.warn miniupnpd[5433]: NAT on upstream router blocks incoming connections set by miniupnpd
Tue Dec 31 00:16:42 2024 daemon.warn miniupnpd[5433]: Turn off NAT on upstream router or change it to full-cone NAT 1:1 type
Tue Dec 31 00:16:42 2024 daemon.warn miniupnpd[5433]: Port forwarding is now disabled

还是不行。懒得研究了…

非公网有必要启用吗

其实 UPnP 在公网环境是否要启用,争议就很大了。开 UPnP 到底方便了什么使用场景? NAT1 的情况下,还需要开启 UPnP 吗?

之前关心的问题,是 aMule 下载一些冷门资源,Switch 上网络测试展示的网络类型。但发现使用 OpenWrt 防火墙那块启用 FullCone NAT 后,Switch 网络监测类型为 NAT A,aMule 的 Kad 虽然显示有防火墙,但下载资源也有速度。

这些只跟 NAT 网络类型有关?与端口转发无关?后面再慢慢研究吧。目前知道 UPnP 无效的情况下,就关掉了。不执着于它了。