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

9

SSHuttle声称它解决了TCP-over-TCP meltdown问题,这是一个广受讨论的关于TCP-over-TCP meltdown的问题。

sshuttle在本地组装TCP流,在ssh会话中以状态方式复用它,并在另一端将其解体回到数据包中。因此,它永远不会进行TCP-over-TCP。只是数据-over-TCP,这是安全的。

但从程序的角度来看,它与目标服务器保持TCP连接,具有其所有功能(如指数超时),这是在其他TCP会话之上的层,因为SSH尚未仅在udp上工作。这非常类似于TCP-over-TCP。

这里的诀窍是什么? SSHuttle真的解决了这个问题吗?

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

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

2个回答

4

sshuttle客户端会设置防火墙规则 (在Linux中使用iptables,因此需要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

当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

TCP内容被拾取并通过SSH连接多路复用到sshuttle服务器,然后再次解复用为连接。client.py中的onaccept_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)

你可以在ssnet.py中看到数据是如何打包的。
我曾经在redsocks中看到过相同的策略(我指设置防火墙规则),该策略旨在将任何TCP连接重定向到SOCKS或HTTPS代理。

3
正如声明所说,这不是TCP-over-TCP。
这是TCP-over-TCP:
First application   
  First end of outer TCP connection  
  First end of inner TCP connection  
   Datagram/packet link  
  Send end of inner TCP connection  
  Second end of outer TCP connection  
Second application  

注意外部TCP连接是如何通过内部TCP连接传输的?
他们正在做的事情是:
First application   
 First end of outer TCP connection  
  Outer end of First TCP connection  
  Inner end of First TCP connection  
    Byte stream link  
  Inner end of Second TCP connection  
  Outer end of Second TCP connection  
Second application 

请注意,没有外部TCP连接通过内部TCP连接进行传输。没有TCP-over-TCP。
有四种明显的方法可以实现:
1. 引导应用程序将TCP连接建立到已分配给系统的IP地址。 2. 将应用程序尝试连接的IP地址分配给系统。 3. 将出站TCP连接NAT到在本地系统上运行的进程。(jfly answer暗示他们是这样做的) 4. 使操作系统将TCP数据包路由到您,并使用用户空间中的TCP实现终止它。

1
你不理解哪一部分?从我的图表可以清楚地看出,每个应用程序都与字节流连接的一端具有完全本地的TCP连接。 - David Schwartz
他们如何“结束”第一个连接,以便内核停止关注它?请参见更新的问题。 - sanmai
好的,他们是如何做到这样的? - sanmai
我不知道他们具体是如何做到的,但有四种明显的方法可供使用:1)引导应用程序建立与已分配给系统的IP地址的TCP连接。2)将应用程序尝试连接到的IP地址分配给系统。3)将出站TCP连接NAT到在本地系统上运行的进程。(我猜测这可能是它使用的方法。)4)使操作系统将TCP数据包路由到您并使用用户空间中的TCP实现终止它。 - David Schwartz
1
我希望你不介意我的编辑。如果有问题请回退。我给你点赞,但是jfly的答案更到位,所以我不得不接受它。 - sanmai
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接