Linux – Command to run a child process “offline” (no external network) on Linux

capabilitieslinuxlxcnamespacenetworking

I have a program I would like to test in offline mode without taking down my actual network. This program would still need to connect to local sockets, including unix domain sockets and loopback. It also needs to listen on loopback and be visible to other apps.

But attempts to connect to a remote machine should fail.

I'd like to have a utility that works like strace/unshare/sudo and simply runs a command with the Internet (and LAN) concealed and everything else still working:

$ offline my-program-to-test

This question has hints at the answer: Block network access of a process?

There are a couple of suggestions there, such as run as another user then manipulate iptables, or unshare -n. But in both cases I don't know the incantation to get unix domain sockets and loopback to be shared with the main system – the answers to that question only tell me how to unshare the entire network.

The program I'm testing still needs to connect to my X server and dbus and even be able to listen on loopback for connections from other apps on the system.

Ideally I'd like to avoid creating chroots or users or VMs or the like, since it becomes just as annoying as unplugging the network cable is. i.e. the point of the question is how can I make this as simple as a sudo.

I'd love the process to run 100% normally except that network calls specifying a non-local address would fail. Ideally keeping the same uid, same homedir, same pwd, same everything except … offline.

I'm using Fedora 18, so unportable Linux answers are just fine (expected, even).

I'm even happy to solve this by writing a C program, if that's what's involved, so answers that involve writing C are fine. I just don't know what syscalls that C program would need to make to revoke the external network access while keeping local network.

Any developer trying to support "offline mode" would probably appreciate this utility!

Best Answer

The traditional answer is to run the program as another user and use iptables -m owner. That way, the network configuration is shared. However, with the advent of namespaces, there is an easier way.

With namespaces, you unshare the network, then create a virtual network link if you need limited network access.

To share unix domain sockets, all you need is to have a recent enough kernel, after this 2010 patch, so 2.6.36 or above (which is the case on all current distributions at the time of writing except RHEL/CentOS).

Run the program in its own IP namespace. Before starting the program, establish a virtual ethernet interface. There doesn't seem to be much documentation; I found the right incantation in a few blogs:

In the snippet below, I use the ns_exec is the this wrapper around setns listed in the man page to bring up the host side of the network link from the process running in the restricted namespace. You don't actually need this: you can set up the host side of the link from outside the restricted namespace. Doing it from inside merely facilitate the flow control, otherwise you need some synchronization to set up the link after it's been established but before starting your program.

unshare -n -- sh -c '
  # Create a virtual ethernet interface called "confined",
  # with the other end called "global" in the namespace of PID 1
  ip link add confined type veth peer name global netns 1

  # Bring up the confined end of the network link
  ip addr add 172.16.0.2/30 dev confined
  ip link set confined up

  # Bring up the global end of the network link
  ns_exec /proc/1/ns/net ifconfig global 172.16.0.1 netmask 255.255.255.252 up

  # Execute the test program
  exec sudo -E -u "$TARGET_USER" "$0" "$@"
' /path/to/program --argument

You need to do all of this as root. The introduction of user namespaces in kernel 3.8 may make this doable with no special permissions, except setting up the global end of the network link.

I don't know how to share localhost between the two namespaces. Virtual ethernet interfaces create a point-to-point bridge. You can use iptables forwarding rules to redirect traffic from/to lo if desired.

Related Question