This post is about safely isolating individual processes in such a way that the only kind of network connection they can successfully establish is through Tor.

A network namespace is logically another copy of the network stack, with its own routes, firewall rules, and network devices. Network Namespaces are administrated with the ip(8) command. Specifically the netns “OBJECT”. Documentation is available through man ip netns.

To create a new network namespace called nstor we use the add command:

† ip netns add nstor

We can list network namespaces with the list command:

† ip netns list
nstor

Notice that there is no default namespace listed, we will look into that later.

We can then execute commands in this new network namespace using the exec command. Let’s spawn a shell just for demonstration purposes:

† ip netns exec nstor bash
† ifconfig
† ifconfig -a
lo: flags=8<LOOPBACK>  mtu 65536
        loop  txqueuelen 1  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

According to the documentation, this lo interface is different from the real lo interface. We can test this using netcat. Using 2 terminal windows:

On the server side:

† nc -l 127.0.0.1 -p 8888

On the client side:

† nc 127.0.0.1 8888

And anything you type in one terminal window will appear in the other. This time execute the client nc inside the new network namespace:

† ip netns exec nstor nc 127.0.0.1 8888

Nothing happens. They really are different.

This in and of itself is useful for cutting off internet connection to individual applications.

As an aside, remember that the list command only returned our new namespace and not the default one:

† ip netns list
nstor

But what if we are inside a network namespace and want to get back to the default?

From the ip-netns man page:

By default a process inherits its network namespace from its parent. Ini‐
tially all the processes share the same default network namespace from the
init process.

Unfortunately it does not seem possible to use the ip tool for this purpose:

ip netns exec automates handling of this configuration, file convention
for network namespace unaware applications, by creating a mount namespace
and bind mounting all of the per network namespace configure files into
their traditional location in /etc.

However there is a different tool called nsenter with a different interface. It uses the setns system call with special files under /proc/$PID/ns/net as an argument, allowing us to join the same network namespace as any other process. Remember that the default network namespace comes from init which is pid 1:

† nsenter --help

Usage:
 nsenter [options] <program> [<argument>...]

Run a program with namespaces of other processes.

Options:
 -t, --target <pid>     target process to get namespaces from
...
 -n, --net[=<file>]     enter network namespace
...

For more details see nsenter(1).

† nsenter -n -t1
† ifconfig
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.3  netmask 255.255.255.0  broadcast 192.168.0.255
...

That’s neat but our goal is to run Tor so some link to the world outside the network namespace is required. For this we can use a Virtual Ethernet pair:

# server
† ip link add vethtor0 type veth peer name vethtor1
† ip address add 10.0.1.1/24 dev vethtor0
† ip link set vethtor0 up
† ip link set vethtor1 netns nstor
† nc -l 10.0.1.2 -p 8888

# client
† ip netns exec nstor bash
† ip address add 10.0.1.2/24 dev vethtor1
† ip link set vethtor1 up
† nc 10.0.1.1 8888

By moving one Virtual Ethernet interface into the network namespace and giving them both an IP address with the same network prefix (10.0.1) we have created a (very limited) connection to the outside world; programs running inside the nework namespace (10.0.1.2) will be able to connect to a program listening on 10.0.1.1 outside of the network namespace.

The final step is to simply run Tor, outside of the network namespace, and have it listen on 10.0.1.1. Any program running inside the network namespace will have no way of contacting the outside world except through Tor, and only if it is configured to connect through 10.0.1.1. This effectively stops any accidental† leaks such as through Flash or Java or some non-browser application you want to torify.

† I say accidental here because as we saw earlier, it is still possible for the root user to switch back to the default namespace. If you are looking for possibly stronger security then check out Firejail.

Full example:

Setup:

# Create a new Network Namespace called nstor
† ip netns add nstor

# Create a new Virtual Ethernet Pair called vethtor0 and vethtor1
† ip link add vethtor0 type veth peer name vethtor1

† ip address add 10.0.1.1/24 dev vethtor0
† ip link set vethtor0 up

# Move vethtor1 interface to the nstor Network Namespace
† ip link set vethtor1 netns nstor

# Now that the vethtor1 interface is in a different Network Namespace, we need
# to use these nested `ip` commands.
† ip netns exec nstor ip address add 10.0.1.2/24 dev vethtor1
† ip netns exec nstor ip link set vethtor1 up

To test:

† cat > torrc << EOF
User tor
PIDFile /var/run/tor/tor.pid
Log notice stderr
DataDirectory /var/lib/tor/data
SOCKSPort 10.0.1.1:9150
RunAsDaemon 1
EOF

 tor -f torrc
 ip netns exec bash

 ifconfig
vethtor1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.1.2  netmask 255.255.255.0  broadcast 0.0.0.0
        ether ae:ea:31:a3:e1:39  txqueuelen 1000  (Ethernet)
        RX packets 90  bytes 74537 (72.7 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 89  bytes 7296 (7.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

† ping 8.8.8.8
connect: Network is unreachable

† curl example.com
curl: (6) Couldn't resolve host 'example.com'

† curl --socks5-hostname 10.0.1.1:9150 example.com
<!doctype html>
<html>
<head>
    <title>Example Domain</title>
...