Skip to content

iptables 转发可能无法正确获取源 IP 地址,导致产生多个 SNAT 规则,转发失败 #91

@Mythologyli

Description

@Mythologyli

起因

被控机为 Azure,Debian 12,安装了 tailscale

添加 iptables 转发规则,将 TCP 65432 端口转发到 1.1.1.1:443,发现端口不通

问题

运行 iptables -t nat -L -v --line-numbers,结果如下:

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1      615 44670 ts-postrouting  all  --  any    any     anywhere             anywhere            
2    65587 4056K MASQUERADE  all  --  any    !docker0  172.17.0.0/16        anywhere            
3        0     0 MASQUERADE  tcp  --  any    any     172.17.0.3           172.17.0.3           tcp dpt:2222
4        0     0 SNAT       tcp  --  any    any     anywhere             one.one.one.one      tcp dpt:https /* BACKWARD 65432->1.1.1.1:443 */ to:100.123.x.x
5        0     0 SNAT       tcp  --  any    any     anywhere             one.one.one.one      tcp dpt:https /* BACKWARD 65432->1.1.1.1:443 */ to:10.1.0.4
6        0     0 SNAT       tcp  --  any    any     anywhere             one.one.one.one      tcp dpt:https /* BACKWARD 65432->1.1.1.1:443 */ to:172.17.0.1

可以看到,出现了多条 SNAT 规则,导致源 IP 没有被正确地替换为 eth0 的 IP 10.1.0.4

查阅源码中的脚本,发现问题所在:

get_ips () {
    install_ip
    IFACE=$(ip route show | grep default | grep metric | awk -F 'dev ' '{print $2}' | awk '{print $1}')
    INET=$(ip address show $IFACE scope global |  awk '/inet / {split($2,var,"/"); print var[1]}')
    INET=$(echo $INET | xargs -n 1 | grep -Eo "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" | sort -u)
}

forward4 () {
    [[ -z $INET ]] && echo "No valid interface ipv4 addresses found" && exit 1
    if [[ $TYPE == "ALL" || $TYPE == "TCP" ]]
    then
        for SNATIP in $INET; do
            $SUDO iptables -t nat -A POSTROUTING -d $REMOTE_IP -p tcp --dport $REMOTE_PORT -j SNAT --to-source $SNATIP -m comment --comment "BACKWARD $LOCAL_PORT->$REMOTE_IP:$REMOTE_PORT"
        done
        $SUDO iptables -t nat -A PREROUTING -p tcp --dport $LOCAL_PORT -j DNAT --to-destination $REMOTE_IP:$REMOTE_PORT  -m comment --comment "FORWARD $LOCAL_PORT->$REMOTE_IP:$REMOTE_PORT"
        # for ipt port traffic monitor
        $SUDO iptables -I FORWARD -p tcp -d $REMOTE_IP --dport $REMOTE_PORT -j ACCEPT -m comment --comment "UPLOAD $LOCAL_PORT->$REMOTE_IP:$REMOTE_PORT"
        $SUDO iptables -I FORWARD -p tcp -s $REMOTE_IP -j ACCEPT -m comment --comment "DOWNLOAD $LOCAL_PORT->$REMOTE_IP:$REMOTE_PORT"
    fi
    if [[ $TYPE == "ALL" || $TYPE == "UDP" ]]
    then
        for SNATIP in $INET; do
            $SUDO iptables -t nat -A POSTROUTING -d $REMOTE_IP -p udp --dport $REMOTE_PORT -j SNAT --to-source $SNATIP -m comment --comment "BACKWARD $LOCAL_PORT->$REMOTE_IP:$REMOTE_PORT"
        done
        $SUDO iptables -t nat -A PREROUTING -p udp --dport $LOCAL_PORT -j DNAT --to-destination $REMOTE_IP:$REMOTE_PORT  -m comment --comment "FORWARD $LOCAL_PORT->$REMOTE_IP:$REMOTE_PORT"
        # for ipt port traffic monitor
        $SUDO iptables -I FORWARD -p udp -d $REMOTE_IP --dport $REMOTE_PORT -j ACCEPT -m comment --comment "UPLOAD-UDP $LOCAL_PORT->$REMOTE_IP:$REMOTE_PORT"
        $SUDO iptables -I FORWARD -p udp -s $REMOTE_IP -j ACCEPT -m comment --comment "DOWNLOAD-UDP $LOCAL_PORT->$REMOTE_IP:$REMOTE_PORT"
    fi
}

脚本试图通过 ip route show | grep default | grep metric | awk -F 'dev ' '{print $2}' | awk '{print $1} 过滤中默认路由对应的网卡,之后再获取正确的源 IP,问题在于路由中不一定有 metric,例如我这台 Azure 上的 ip route 输出:

default via 10.1.0.1 dev eth0 
......

这导致没有获取到网卡,之后就获取到了多个源 IP,产生错误

解决方案

作为用户,最简单的解决方案是在被控机上安装 ifmetric,然后运行:

ifmetric eth0 1

这样路由会变成:

default via 10.1.0.1 dev eth0 metric 1

这样就能正确获取到 IP 了。注意,如果你有多个路由,依靠 metric 确定优先级,这样可能会有副作用!请先研究清楚再操作

开发者可以考虑将面板脚本中的代码修改为:ip route show | grep default | awk -F 'dev ' '{print $2}' | awk '{print $1},或者干脆直接用 MASQUERADE?这样源 IP 就无所谓了

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions