可用于商业化售卖的 Proxmox VE 预先分配端口的 NAT 解决方案

使用预先分配端口的方式来给每个主机分配一段端口实现 NAT 端口转发的解决方案。

商业化环境

WHMCS + Proxmox VE + Proxmox Addon。

简述

Proxmox VE 使用 KVM 虚拟化,会带来更小的性能损耗。不过相对于无法扩展的插件来说,使其适配 NAT 会产生一些问题。

这不是一个新颖的解决方案,它有点弯路,但是很实用。

原理

部署或虚拟化一个 NAT 网关,使用预先分配的 iptables 规则,即可完成 NAT 转发。

计算方法

function calculatePortRanges(int $hostNumber, int $startPort = 20000, int $portPerHost = 10)
{
    // 计算端口范围
    $startPortNumber = $startPort + ($hostNumber * ($portPerHost + 1));
    $endPortNumber = $startPortNumber + $portPerHost;

    // 输出端口范围
    echo "主机号 {$hostNumber} 对应的端口范围是 {$startPortNumber} 到 {$endPortNumber}。";

    $ssh_port = $startPortNumber;
    $rdp_port = $startPortNumber + 1;
    $real_start = $startPortNumber + 2;

    echo "内: 22,3389,{$real_start}-{$endPortNumber}";
    echo "外: {$ssh_port},{$rdp_port},{$real_start}-{$endPortNumber}";
}

在 calculatePortRanges 的形参中,$hostNumber 为主机号,$startPort 为起始端口,$portPerHost 为每个主机分配的端口个数。

随后,将取分配到该主机号到第一个可用端口用于 SSH,对应 IP 地址的 22 端口,第二个端口用于 远程桌面,对应 IP 地址的 3389 端口。

剩下的端口将映射到 IP 地址的相同端口。
也就是说,如果内外网端口必须相同。

缺点

此方法无法针对每个主机设置不同的可用端口个数,且后期拓展性差。

但是确实很方便。

生成的 iptables 命令

我们可以将这个计算方法写成一个脚本。随后将它运行在虚拟网关。

#!/bin/bash

if [[ $# -lt 3 ]]; then
    echo "参数错误!请提供正确的网卡名称、网络段、起始端口和每个主机号的端口数量,本机 IP。如果需要测试模式,请在最后加上 test_mode 参数。"
    echo "示例:sudo bash $0 eth0 192.168.0.0/24 21000 10"
    exit 1
fi

# 解析网络段、起始端口和每个主机号的端口数量
subnet=$2
startPort=$3
portPerHost=$4
device=$1

# 检测网络段是否 CIDR
if [[ $subnet != *"/"* ]]; then
    echo "网络段必须是 CIDR 格式!"
    exit 1
fi  


# 验证起始端口和每个主机号的端口数量大于等于0
if [[ $startPort -lt 0 || $portPerHost -lt 0 ]]; then
    echo "起始端口和每个主机号的端口数量必须大于等于0!"
    exit 1
fi


# 传递的参数:IP地址和起始端口号
ip=$2
echo "CIDR 是 ${ip}。"


# 去除 ip 的主机号和网段
ip=${ip%.*}

portPerHost=$((portPerHost + 1))

# 启用IP转发
echo 1 > /proc/sys/net/ipv4/ip_forward

# 清除旧的iptables规则
iptables -t nat -F
iptables -t nat -X
iptables -t nat -Z
iptables -F
iptables -X
iptables -Z

# 设置默认策略
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

# NAT
iptables -t nat -A POSTROUTING -s ${subnet} -o ${device} -j MASQUERADE

# 计算每个主机号的端口范围
function calculatePortRanges() {
    hostNumber=$1

    startPortNumber=$((startPort + (hostNumber * portPerHost)))
    endPortNumber=$((startPortNumber + portPerHost - 1))

    echo "IP地址 ${ip}.${hostNumber} 对应的端口范围是 ${startPortNumber} 到 ${endPortNumber}。"
}

# 创建新的iptables规则
function createPortForwardingRule() {
    set -x

    hostNumber=$1

    startPortNumber=$((startPort + (hostNumber * portPerHost)))
    endPortNumber=$((startPortNumber + portPerHost - 1))

    echo "IP ${ip}.${hostNumber} 的 SSH 端口是 ${startPortNumber}。"
    # SSH端口转发
    iptables -t nat -A PREROUTING -i ${device} -p tcp --dport ${startPortNumber} -j DNAT --to ${ip}.${hostNumber}:22
    iptables -t nat -A PREROUTING -i ${device} -p udp --dport ${startPortNumber} -j DNAT --to ${ip}.${hostNumber}:22

    # RDP端口转发
    echo "IP ${ip}.${hostNumber} 的 RDP 端口是 $((startPortNumber + 1))。"
    iptables -t nat -A PREROUTING -i ${device} -p tcp --dport $((startPortNumber + 1)) -j DNAT --to ${ip}.${hostNumber}:3389
    iptables -t nat -A PREROUTING -i ${device} -p udp --dport $((startPortNumber + 1)) -j DNAT --to ${ip}.${hostNumber}:3389

    # 实际端口范围转发

    startPortNumber=$((startPortNumber + 2))
    echo "起始端口号是 ${startPortNumber}。"
    endPortNumber=$((endPortNumber))
    echo "结束端口号是 ${endPortNumber}。"

    # 实际端口范围转发
    for ((portNumber = startPortNumber; portNumber <= endPortNumber; portNumber++)); do
        echo "IP ${ip}.${hostNumber} 的端口是 ${portNumber}。"
        iptables -t nat -A PREROUTING -i ${device} -p tcp --dport ${portNumber} -j DNAT --to ${ip}.${hostNumber}:${portNumber}
        iptables -t nat -A PREROUTING -i ${device} -p udp --dport ${portNumber} -j DNAT --to ${ip}.${hostNumber}:${portNumber}
    done
    
    set +x

}

# 循环遍历主机号
for hostNumber in $(seq 1 254); do
    if [ "$5" == "test_mode" ]; then
        calculatePortRanges $hostNumber
    else
        createPortForwardingRule $hostNumber
    fi
done

# 保存iptables规则
iptables-save > /etc/iptables.rules

调用方法:bash your_name.sh eth0 192.168.1.0/24 21000 10。

注意:此脚本没有计算子网的可用地址数量。

虚拟机网络设置

将虚拟机的网关地址设置为虚拟网关的地址,随后即可开始验证端口连通性。

前端计算 IP 地址对应的端口范围

您可以查看本页面源代码。线上演示:https://stack.laecloud.com/p/ip_calc

应用此解决方案

您可以随意修改并二次发布我们的代码,以应用解决方案。

Github 仓库:https://github.com/iVampireSP/proxmox-ve-pre-defined-port-nat

取得联系