A Blog    Archive    Feed    About

Tor (client) in Docker for transparent proxying

I wanted to create an environment where the only way a container can access network is through Tor. This means I can run a program (e.g. browser) and be certain that it have no way revealing the connection. That is, I want to run some of the applications through Tor, but not others.

All the possible related work is done by @jfrazelle. One can transparent proxy all the traffic or create a local proxy

The latter is almost what we want except that the application must speak SOCKS, and is not forced to use the proxy. There is even a video about this latter approach.


The idea is to start a new container with enough capability to change iptables rules. One can simply copy the transparent proxy scripts form the Tor wiki and run it inside that container. Any process in this container must go through the iptables rules, thus use Tor.

You can check my implementation here. To start it:

docker run -d --cap-add NET_ADMIN --name=tor root_tor

The container sets some iptables rules after start; at least the following is needed:

iptables -t nat -A OUTPUT -m owner --uid-owner $_tor_uid -j RETURN
iptables -t nat -A OUTPUT -p tcp --syn -j REDIRECT --to-ports 9040
iptables -A OUTPUT -m owner --uid-owner $_tor_uid -j ACCEPT
iptables -A OUTPUT -d -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -j REJECT

Check (from inside) that it really acts as a transparent proxy:

curl -k https://check.torproject.org/api/ip

With recent enough docker, one can create a special network with this container; with older version (from 1.0), one can still put containers (e.g. Chrome) into the network namespace of this Tor container.

docker run --rm -it \
    --net=container:tor \
    -e DISPLAY=:0 -v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0:ro \
    -u=$(id -u) -e HOME=/tmp \
    --name=chrome \
    jess/chrome --no-sandbox

Happy browsing! Beware: Chrome can SIGBUS because /dev/shm in Docker is small (only 64M).

Docker + Chromium = SIGBUS

Tales of a bughunt

All began with reading @jfrazelle stuffs about dockerizing desktop apps. Some programs are not so good citizens, i.e. Okular leaves a KDE daemon running. Another serious contender is my browser, Chromium, which after the Google voice blob thing seemed appropriate to contain. Something that is more private than private browsing and cannot use my webcam/sound card would be really nice…

After learning Docker X sharing (simple bind mount), it should be easy to create or use someone else’s docker. Run it like

docker run --rm -it -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:ro chromium_gui

and check some website: Aw, Snap!. From previous experience, it may be SIGSEGV Let’s check as usual: with gdb and strace:

#strace -ff -o log chromium --no-sandbox --user-data-dir=/tmp
log.143:--- SIGBUS {si_signo=SIGBUS, si_code=BUS_ADRERR, si_addr=0x7f3355a2b000} ---

SIGBUS on x86? gdb backtrace seems perfectly legal. Google was not really helpful on this. And this is the same even for ssh X forward. To check that this is really a Docker issue, I went insane:

mount --bind / /mnt/root/
mount --bind /sys/ /mnt/root/sys/
mount --bind /proc/ /mnt/root/proc/
mount --bind /dev/ /mnt/root/dev/
chroot /mnt/root/
chromium --no-sandbox --user-data-dir=/tmp

Of course, it worked. This bind mounts in Docker? Works too; turns out, only /dev is needed (on Debian jessie). Fortunately Debian sid (the other version I use) differs in that /dev/shm is a symlink to /run/shm. Mounting /dev worked only jessie; on sid I got something like:

Creating shared memory in /dev/shm/.org.chromium.Chromium.sSLYBL failed: Permission denied
Unable to access(W_OK|X_OK) /dev/shm: Permission denied
This is frequently caused by incorrect permissions on /dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix.

This leads to this Docker issue and one possible workaround for the SIGBUS:

-v /dev/shm:/dev/shm -v /run/shm:/run/shm

Reduced steps to reproduce

What now? Is it that 64M /dev/shm is not enough for chromium? What if mount -o remount,size=64M /dev/shm? Aw, snap! Unfortunately watching the output of df came to my mind only this time. Chromium uses /dev/shm, but it does not really need shared memory; mount --bind /tmp/ /dev/shm/ works perfectly too. Getting this crash is not that far fetched; I managed get Aw, snap! on a machine with 2G RAM by leaving the same amount of tabs I usually do on my >8G machines.

Lessons learned

  • Google and Stack Overflow can be horribly wrong
  • SIGBUS-ing on tmpfs full is an awful error message
  • Watching the health of the system/docker container would have saved tons of time