sshuttle 如何避免 TCP-over-TCP 诅咒?

san*_*mai 7 networking tcp congestion-control

sshuttle 声称它解决了很多讨论过TCP-over-TCP 崩溃问题

sshuttle 在本地组装 TCP 流,通过 ssh 会话有状态地多路复用它,然后在另一端将其分解回数据包。所以它永远不会做 TCP-over-TCP。它只是通过 TCP 传输的数据,这是安全的。

但是从程序的角度来看,它维护与目标服务器的 TCP 连接及其附带的所有内容(读取指数超时),这是与其他 TCP 会话分层的,因为 SSH 还不能仅在udp. 这很像 TCP-over-TCP。

这里的诀窍是什么?问题真的通过sshuttle解决了吗?

我尝试阅读源代码,但到目前为止还没有找到答案。

更重要的是,他们究竟是如何做到的?如果想在准系统中重新实现它,应该从哪里寻找灵感?

jfl*_*fly 4

sshuttle客户端设置防火墙规则(Linux 中的 iptables,这就是sshuttle客户端需要 root 权限的原因)将某些传出 TCP 连接重定向到本地端口(默认为 12300),您可以在启动 sshuttle 时看到此过程:

firewall manager: starting transproxy.
>> iptables -t nat -N sshuttle-12300
>> iptables -t nat -F sshuttle-12300
>> iptables -t nat -I OUTPUT 1 -j sshuttle-12300
>> iptables -t nat -I PREROUTING 1 -j sshuttle-12300
>> iptables -t nat -A sshuttle-12300 -j RETURN --dest 127.0.0.0/8 -p tcp
>> iptables -t nat -A sshuttle-12300 -j REDIRECT --dest 0.0.0.0/0 -p tcp --to-ports 12300 -m ttl ! --ttl 42
Run Code Online (Sandbox Code Playgroud)

并在 sshuttle 退出时删除 iptables nat 规则,

>> iptables -t nat -D OUTPUT -j sshuttle-12300
>> iptables -t nat -D PREROUTING -j sshuttle-12300
>> iptables -t nat -F sshuttle-12300
>> iptables -t nat -X sshuttle-12300
Run Code Online (Sandbox Code Playgroud)

TCP 内容被拾取并通过 ssh 连接多路复用到sshuttle服务器,然后再次多路分解为连接。client.pyonaccept_tcpin中的函数执行复用:

def onaccept_tcp(listener, method, mux, handlers):
    global _extra_fd
    try:
        sock, srcip = listener.accept()
    except socket.error as e:
        if e.args[0] in [errno.EMFILE, errno.ENFILE]:
            debug1('Rejected incoming connection: too many open files!\n')
            # free up an fd so we can eat the connection
            os.close(_extra_fd)
            try:
                sock, srcip = listener.accept()
                sock.close()
            finally:
                _extra_fd = os.open('/dev/null', os.O_RDONLY)
            return
        else:
            raise

    dstip = method.get_tcp_dstip(sock)
    debug1('Accept TCP: %s:%r -> %s:%r.\n' % (srcip[0], srcip[1],
                                              dstip[0], dstip[1]))
    if dstip[1] == sock.getsockname()[1] and islocal(dstip[0], sock.family):
        debug1("-- ignored: that's my address!\n")
        sock.close()
        return
    chan = mux.next_channel()
    if not chan:
        log('warning: too many open channels.  Discarded connection.\n')
        sock.close()
        return
    mux.send(chan, ssnet.CMD_TCP_CONNECT, b'%d,%s,%d' %
             (sock.family, dstip[0].encode("ASCII"), dstip[1]))
    outwrap = MuxWrapper(mux, chan)
    handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
    expire_connections(time.time(), mux)
Run Code Online (Sandbox Code Playgroud)

您可以在ssnet.py中看到数据是如何打包的。

我在redsocks中看到了相同的策略(我的意思是设置防火墙规则),旨在将任何 TCP 连接重定向到 SOCKS 或 HTTPS 代理。