gpt-4o 写的,我润色润色,好笑的是找了一万种解决方案,结果最后 AI 写的是唯一正常工作的
Openwrt 24.10.1
这次用 nf 桌子,首先检查下有没有 nat 表:
nft list table nat
如果输出显示了 nat 表和里面的链(如 PREROUTING、POSTROUTING),说明已经有了,不然就新建一个:
nft add table nat
nft add chain nat PREROUTING { type nat hook prerouting priority 0 \; }
nft add chain nat POSTROUTING { type nat hook postrouting priority 100 \; }
nft add chain nat OUTPUT { type nat hook output priority 100 \; }
然后就是老生常谈的脚本了。其他的可以参考后面的部分,这次是放开了所有端口,兼容旁路由情况下连接主路由设备可以NAT回环但直连旁路由的设备不行(网线直连)
#!/bin/ash
# 配置区
INTERNAL_IP="192.168.31.2" # 内网服务器IP
IP_FILE="/etc/TigerBeanst/last_public_ip" # 存储上次公网IP的文件路径
# 获取当前公网IP(使用双源校验)
get_public_ip() {
local ip1
ip1=$(curl -4 -s --connect-timeout 5 http://4.ipw.cn || echo "N/A")
# ip2=$(curl -4 -s --connect-timeout 5 http://ipv4.ip.sb || echo "N/A")
if echo "$ip1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then
echo "$ip1"
else
echo "ERROR"
fi
}
# 删除旧规则函数,使用句柄删除更准确
delete_rules() {
local old_ip="$1"
local internal_ip="$2"
# PREROUTING 链
for proto in tcp udp; do
nft -a list chain nat PREROUTING | \
awk -v ip="$old_ip" -v proto="$proto" '
$0 ~ ip && $0 ~ proto {
for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)
}
' | while read -r handle; do
[ -n "$handle" ] && nft delete rule nat PREROUTING handle "$handle"
done
done
# POSTROUTING 链
for proto in tcp udp; do
nft -a list chain nat POSTROUTING | \
awk -v ip="$internal_ip" -v proto="$proto" '
$0 ~ ip && $0 ~ proto {
for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)
}
' | while read -r handle; do
[ -n "$handle" ] && nft delete rule nat POSTROUTING handle "$handle"
done
done
# OUTPUT 链
for proto in tcp udp; do
nft -a list chain nat OUTPUT 2>/dev/null | \
awk -v ip="$old_ip" -v proto="$proto" '
$0 ~ ip && $0 ~ proto {
for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)
}
' | while read -r handle; do
[ -n "$handle" ] && nft delete rule nat OUTPUT handle "$handle"
done
done
}
# 删除所有匹配内网IP的规则(用于无旧IP时清理)
delete_rules_all() {
local internal_ip="$1"
# PREROUTING 链
nft -a list chain nat PREROUTING | \
awk -v ip="$internal_ip" '
$0 ~ ip {
for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)
}
' | while read -r handle; do
[ -n "$handle" ] && nft delete rule nat PREROUTING handle "$handle"
done
# POSTROUTING 链
nft -a list chain nat POSTROUTING | \
awk -v ip="$internal_ip" '
$0 ~ ip {
for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)
}
' | while read -r handle; do
[ -n "$handle" ] && nft delete rule nat POSTROUTING handle "$handle"
done
# OUTPUT 链
nft -a list chain nat OUTPUT 2>/dev/null | \
awk -v ip="$internal_ip" '
$0 ~ ip {
for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)
}
' | while read -r handle; do
[ -n "$handle" ] && nft delete rule nat OUTPUT handle "$handle"
done
}
# 添加新规则函数
add_rules() {
local current_ip="$1"
local internal_ip="$2"
for proto in tcp udp; do
# 添加PREROUTING链的DNAT规则
nft add rule nat PREROUTING ip daddr "$current_ip" ip protocol "$proto" dnat to "$internal_ip"
# 添加POSTROUTING链的MASQUERADE规则
nft add rule nat POSTROUTING ip daddr "$internal_ip" ip protocol "$proto" masquerade
# 添加OUTPUT链的DNAT规则,处理本地发起访问公网IP的请求
nft add rule nat OUTPUT ip daddr "$current_ip" ip protocol "$proto" dnat to "$internal_ip"
done
}
# 主程序
CURRENT_IP=$(get_public_ip)
LAST_IP=$(cat "$IP_FILE" 2>/dev/null || echo "NONE")
if [ "$CURRENT_IP" = "ERROR" ]; then
echo "获取公网IP失败,保持现有规则"
exit 1
fi
if [ "$LAST_IP" = "NONE" ] || [ -z "$LAST_IP" ]; then
echo "无旧IP记录,尝试清理所有匹配内网IP的规则..."
delete_rules_all "$INTERNAL_IP"
elif [ "$CURRENT_IP" = "$LAST_IP" ]; then
echo "公网IP未变化($CURRENT_IP),无需更新"
exit 0
else
echo "检测到公网IP变更:
旧IP: $LAST_IP
新IP: $CURRENT_IP
开始更新防火墙规则..."
delete_rules "$LAST_IP" "$INTERNAL_IP"
echo "旧IP($LAST_IP)规则已清除"
fi
add_rules "$CURRENT_IP" "$INTERNAL_IP"
echo "$CURRENT_IP" > "$IP_FILE"
echo "新规则设置完成,当前生效IP: $CURRENT_IP"
Openwrt 23.05
创建回环配置脚本
因为对我来说,我所有在外部访问的服务实际会通过子域名区分,在内网被反向代理,所以我只需要配置一个端口就行,有多个的话应该依葫芦画瓢就行
注意修改 internal_ip 和 port
cat << 'EOF' > /etc/nat-loopback.sh
#!/bin/sh
# Get current public IP
public_ip=$(curl -s 4.ipw.cn)
# Internal server IP and port
internal_ip="192.168.31.1"
port="66666"
# Flush existing rules to avoid duplication
iptables -t nat -F PREROUTING
iptables -t nat -F POSTROUTING
iptables -F FORWARD
# Add NAT loopback rules
iptables -t nat -A PREROUTING -d $public_ip -j DNAT --to-destination $internal_ip
iptables -t nat -A POSTROUTING -s $internal_ip -j MASQUERADE
iptables -A FORWARD -d $internal_ip -p tcp --dport $port -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
EOF
然后授予运行权限
chmod +x /etc/nat-loopback.sh
防火墙模块
这一步不一定需要,但可能会因为缺模块导致无法工作,如果直接运行 /etc/nat-loopback.sh 没问题,就没必要走这一步
opkg update
opkg install ip-full iptables-mod-extra kmod-ipt-nat kmod-ipt-extra kmod-ipt-conntrack kmod-nf-conntrack
安装完后重启防火墙
/etc/init.d/firewall restart
测试
直接执行这个指令文件就行
/etc/nat-loopback.sh
如果没有报错,就尝试访问之前因为没有配置好环回导致出问题的地址,如果也正常,就可以把这个指令加到计划任务里了(因为涉及到获取公网IP,你要是买的固定IP算我没说)
*/5 * * * * /etc/nat-loopback.sh
Openwrt 18.06(小米路由器)
依葫芦画瓢,缺一些模块的话自己上网找下
#!/bin/sh
# ==================== 配置区 ====================
INTERNAL_IP="192.168.31.2" # 旁路由IP
IP_FILE="/etc/config/TigerBeanst/nat_loopback_last_public_ip"
# 以下这行是专门给米米路由器的,避免漫游到 mesh 路由的时候失效
echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables
# ==================== 函数定义 ====================
log_info() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1"
}
log_error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1"
}
get_public_ip() {
local ip1
ip1=$(curl -s --connect-timeout 5 cip.cc | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "N/A")
if echo "$ip1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then
echo "$ip1"
else
echo "ERROR"
fi
}
check_rules_exist() {
iptables-save -t nat | grep -q "DNAT.*${INTERNAL_IP}"
}
delete_rules() {
local target_ip="$1"
if [ -n "$target_ip" ] && [ "$target_ip" != "NONE" ]; then
log_info "正在删除IP ${target_ip} 相关的NAT规则..."
iptables-save -t nat | grep -E "DNAT.*${target_ip}" | sed 's/-A /-D /g' | while read rule; do
[ -n "$rule" ] && iptables -t nat $rule 2>/dev/null
done
iptables-save -t nat | grep -E "(SNAT|MASQUERADE).*${INTERNAL_IP}" | sed 's/-A /-D /g' | while read rule; do
[ -n "$rule" ] && iptables -t nat $rule 2>/dev/null
done
log_info "旧规则清除完成"
fi
}
add_nat_rules() {
local public_ip="$1"
log_info "添加全端口NAT回环规则 (IP: ${public_ip})"
for protocol in tcp udp; do
iptables -t nat -I prerouting_rule 1 -d "${public_ip}/32" -p "$protocol" -j DNAT --to-destination "$INTERNAL_IP"
iptables -t nat -I POSTROUTING 1 -d "$INTERNAL_IP" -p "$protocol" -j MASQUERADE
iptables -t nat -I POSTROUTING 1 -s 192.168.1.0/24 -d "$INTERNAL_IP" -p "$protocol" -j SNAT --to-source 192.168.31.1
iptables -t nat -I POSTROUTING 1 -s 192.168.31.0/24 -d "$INTERNAL_IP" -p "$protocol" -j SNAT --to-source 192.168.31.1
case "$protocol" in
tcp) protocol_upper="TCP" ;;
udp) protocol_upper="UDP" ;;
*) protocol_upper="$protocol" ;;
esac
log_info "✓ ${protocol_upper} 全端口规则已添加 (已提权至链首)"
done
}
# ==================== 主程序 ====================
log_info "==========================================="
log_info "全端口NAT回环脚本启动"
log_info "安全说明:主路由端口转发配置限制实际开放端口"
log_info "==========================================="
if ! echo "$INTERNAL_IP" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then
log_error "旁路由IP格式错误: $INTERNAL_IP"
exit 1
fi
mkdir -p "$(dirname "$IP_FILE")" 2>/dev/null
CURRENT_IP=$(get_public_ip)
LAST_IP=$(cat "$IP_FILE" 2>/dev/null || echo "NONE")
if [ "$CURRENT_IP" = "ERROR" ]; then
log_error "获取公网IP失败,保持现有配置"
exit 1
fi
# IP未变化时,检查规则是否实际存在
if [ "$CURRENT_IP" = "$LAST_IP" ]; then
if check_rules_exist; then
log_info "公网IP未变化 ($CURRENT_IP),规则存在,无需更新"
exit 0
else
log_info "公网IP未变化 ($CURRENT_IP),但规则缺失,重新添加"
fi
fi
log_info "公网IP变更: ${LAST_IP:-无} → $CURRENT_IP"
if [ "$LAST_IP" != "NONE" ] && [ -n "$LAST_IP" ]; then
delete_rules "$LAST_IP"
fi
add_nat_rules "$CURRENT_IP"
echo "$CURRENT_IP" > "$IP_FILE"
log_info "==========================================="
log_info "NAT回环更新完成!"
log_info "当前公网IP: $CURRENT_IP"
log_info "有效端口: 由主路由端口转发配置决定"
log_info "==========================================="
yoshiki Chrome 128.0.0.0
试了好多种办法,你这个成功了,感谢!
ssj Chrome 131.0.0.0
本来我也想尝试这个方法的, 问了gpt这段代码的原理后, 自己稍加思考, 不用代码也能实现, 我是卡在内网和外网使用相同的域名都可以访问到内部服务的问题,外网域名访问的问题已经解决了, 要解决内网也使用域名访问, 在openwrt的端口转发的地方:之前是lan转到wan,现在新增一条wan转到lan,启用nat回环,内部ip,Reflection zones设置为wan
TigerBeanst Edge Chromium 131.0.0.0
我最开始也是单纯设置区域,但没成功hhh