8

sshuttle claims that it solves much discussed problem of TCP-over-TCP meltdown.

sshuttle assembles the TCP stream locally, multiplexes it statefully over an ssh session, and disassembles it back into packets at the other end. So it never ends up doing TCP-over-TCP. It’s just data-over-TCP, which is safe.

But from the point of view of a program it maintains a TCP connection to a target server with all that comes with it (read exponential timeouts), which is layered about other TCP session since SSH doesn't yet just work on udp. This very much looks like TCP-over-TCP.

What is the trick here? Is the problem really solved by sshuttle?

I tried reading source code, but so far didn't find the answer.

More importantly, how exactly do they do it? If one wants to reimplement it in barebones, where one should look for inspiration?

4

2 回答 2

3

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

并在 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 内容被拾取并通过与sshuttle服务器的 ssh 连接进行多路复用,然后再次解复用为连接。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)

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

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

于 2017-01-02T17:13:38.390 回答
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. 诱导应用程序与已分配给系统的 IP 地址建立 TCP 连接。
  2. 将应用程序尝试连接的 IP 地址分配给系统。
  3. NAT 出站 TCP 连接到本地系统上运行的进程。(jfly 回答暗示这就是他们所做的)
  4. 让操作系统将 TCP 数据包路由给您,并通过您在用户空间中的 TCP 实现来终止它。
于 2017-01-02T12:55:29.137 回答