看过 freeshell 源码的同学可能发现 freeshell 的入站端口映射使用了 SNAT。DNAT 不就够用了吗?事实上,freeshell 的 SNAT 是为了处理一种特殊情况:hairpin。hairpin 是 “发卡” 的意思,代表路由了一圈又绕回来的包。一般情况下,Linux 协议栈的反向路由检查模块会把这种 hairpin 包丢掉。但是,freeshell 用户可能需要访问公网的映射端口,例如连接 ssh.freeshell.ustc.edu.cn:30xxx

由于反向路由检查是一种网络安全措施,为了这个取消反向路由检查不值得。因此我们在 freeshell 入站端口映射服务器上对这种 hairpin 的连接做了 SNAT,也就是同一物理节点上发出的 hairpin 连接,看到的源 IP 是入站端口映射服务器。其他连接的源 IP 不改变。


function add($local_port, $remote_ip, $remote_port, $protocol, $snat_src_ip) {
    $local_ips = array('202.141.160.99', '202.141.176.99');
    foreach ($local_ips as $local_ip) {
        $this->dnat_rules[] = "-d $local_ip/32 -p $protocol --dport $local_port -j DNAT --to-destination $remote_ip:$remote_port";
    }
    $this->snat_rules[] = "-s $snat_src_ip/32 -d $remote_ip/32 -p $protocol --dport $remote_port -j MASQUERADE";
    return true;
}