[anti-censorship-team] Turbo Tunnel in obfs4proxy (survives TCP connection termination)
David Fifield
david at bamsoftware.com
Mon Oct 21 23:34:01 UTC 2019
I posted this at https://github.com/net4people/bbs/issues/14#issuecomment-544747519
but it's relevant to anti-censorship in Tor.
Here are proof-of-concept branches implementing the turbo tunnel idea in
obfs4proxy, one using kcp-go/smux and one using quic-go:
* https://dip.torproject.org/dcf/obfs4/tree/reconnecting-kcp
* https://dip.torproject.org/dcf/obfs4/tree/reconnecting-quic
As diffs:
* [Changes needed to add kcp-go/smux to plain obfs4proxy](https://dip.torproject.org/dcf/obfs4/compare/aa7313b7eb78b7546a10f9612fb313280223612c...b5d6895542b8fce92973901958b2d7a2be1f2ebd)
* [Changes needed to adapt kcp-go/smux to quic-go](https://dip.torproject.org/dcf/obfs4/compare/b5d6895542b8fce92973901958b2d7a2be1f2ebd...a3d32cb152c9bacfc31600b9689e4d3591acc376)
Using either of these branches, your circumvention session is decoupled
from any single TCP connection. If a TCP connection is terminated, the
obfs4proxy client will establish a new connection and pick up where it
left off. An error condition is signaled to the higher-level application
only when there's a problem establishing a new connection. Otherwise,
transient connection termination is invisible (except as a brief
increase in RTT) to Tor and whatever other application layers are being
tunnelled.
I did a small experiment showing how a Tor session can persist, despite
the obfs4 layer being interrupted every 20 seconds. I configured the
"little bastard" connection terminator to forward from a local port to a
remote bridge, and terminate connections after 20 seconds.
```
lilbastard$ cargo run -- -w 20 127.0.0.1:3000 192.81.135.242:4000
```
On the bridge, I ran tor using either plain obfs4proxy, or one of the
two turbo tunnel branches. (I did the experiment once for each of the
three configurations.)
```
DataDirectory datadir.server
SOCKSPort 0
ORPort auto
BridgeRelay 1
AssumeReachable 1
PublishServerDescriptor 0
ExtORPort auto
ServerTransportListenAddr obfs4 0.0.0.0:4000
ServerTransportPlugin obfs4 exec ./obfs4proxy -enableLogging -unsafeLogging -logLevel DEBUG
# ServerTransportPlugin obfs4 exec ./obfs4proxy.kcp -enableLogging -unsafeLogging -logLevel DEBUG
# ServerTransportPlugin obfs4 exec ./obfs4proxy.quic -enableLogging -unsafeLogging -logLevel DEBUG
```
On the client, I configured tor to use the corresponding obfs4proxy
executable, and connect to the bridge through the "little bastard"
proxy. (If you do this, your bridge fingerprint and cert will be
different.)
```
DataDirectory datadir.client
SOCKSPort 9250
UseBridges 1
Bridge obfs4 127.0.0.1:3000 94E4D617537C3E3CEA0D1D6D0BC852B5A7613B77 cert=6rB8kVd981U0G2b9nXioB5o0Zu7tDpDkoZyPe2aCmqFzGmfaSiNIfQvkJABakH+DfYwWRw iat-mode=0
ClientTransportPlugin obfs4 exec ./obfs4proxy -enableLogging -unsafeLogging -logLevel DEBUG
# ClientTransportPlugin obfs4 exec ./obfs4proxy.kcp -enableLogging -unsafeLogging -logLevel DEBUG
# ClientTransportPlugin obfs4 exec ./obfs4proxy.quic -enableLogging -unsafeLogging -logLevel DEBUG
```
Then, I captured traffic for 90 seconds while downloading a video file
through the tor proxy.
In the "plain obfs4proxy" case, the download stopped after the first
connection termination at 20 s. Every 20 s after that, there was a small
amount of activity, which was tor reconnecting to the bridge (and the
resulting obfs4 handshake). But it didn't matter, because tor has
already signaled the first connection termination to the application
layer, which gave up:
```
curl: (18) transfer closed with 111535615 bytes remaining to read
```
In comparison, the "kcp" and "quic" cases kept on downloading, being
only momentarily delayed by a connection termination.
Notes:
* How this works architecturally, on the client side, we replace the
original TCP Dial call with either kcp.NewConn2 or quic.Dial, over an
abstract packet-sending interface (clientPacketConn).
clientPacketConn runs a loop that repeatedly connects to the same
destination and exchanges packets (represented as length-prefixed
blobs in a TCP stream) as long as the connection is good, reporting
an error only when a connection attempt fails. On the server side, we
replace the TCP Listen call with either kcp.ServeConn or quic.Listen,
over an abstract serverPacketConn. serverPacketConn opens a single
TCP listener, takes length-prefixed packets from *all* the TCP
streams that arrive at the listener, and feeds them into a *single*
KCP or QUIC engine. Whenever we need to send a packet for a
particular connection ID, we send it on the TCP stream that most
recently sent us a packet for that connection ID.
* There's no need for this functionality to be built into obfs4proxy
itself. It could be done as a separate program:
```
------------ client ------------ ------------ bridge ------------
tor -> turbotunnel -> obfs4proxy -> internet -> obfs4proxy -> turbotunnel -> tor
```
But this kind of process layering is cumbersome with pluggable transports.
* I'm passing a blank client IP address to the pt.DialOr call—this
information is used for geolocation in Metrics graphs. That's because
an OR connection no longer corresponds to a single incoming IP
address with its single IP address—instead it corresponds to an
abstract "connection ID" that remains constant across potentially
many TCP connections. In order to make this work, you would have to
define some heuristic such as "the client IP address associated with
the OR connection is that of the first TCP connection that carried
that connection ID."
More information about the anti-censorship-team
mailing list