Separate DNS configuration in each network namespace

Solution

You can use ip netns exec with bash instead of using nsenter, i.e.:

ip netns enter [namespace name] bash

This will allow you to enter an interactive shell session where the namespace-specific network configuration files are automatically bind-mounted to their default (global) locations (without affecting other sessions).

Explanation

The following is taken from the ip netns man page:

For applications that are aware of network namespaces, the convention is to look for global network configuration files first in /etc/netns/**NAME/ then in **/etc/. For example, if you want a different version of /etc/resolv.conf for a network namespace used to isolate your vpn you would name it /etc/netns/myvpn/resolv.conf.

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.

Note in particular the distinction between network-namespace aware applications and network-namespace unaware applications.

The nsenter man pages, on the other hand, do not seem to mention this distinction (in particular I searched for the strings "aware", "resolv", ".conf" and "/etc" and found no results). This seems to suggest that the nsenter utility does not perform the same kind of automatic handling of namespace unaware applications.

Additional Comments

In addition to Network Namespaces, you might also want to look at User Namespaces and Mount Namespaces. And if you're going to want further isolation beyond DNS you might also want to consider containerization, e.g. LXC Containers or Docker.


Just look at what is doing ip netns exec test ... in your situation, using strace.

Excerpt:

# strace  -f ip netns exec test sleep 1 2>&1|egrep '/etc/|clone|mount|unshare'|egrep -vw '/etc/ld.so|access'
unshare(CLONE_NEWNS)                    = 0
mount("", "/", 0x55f2f4c2584f, MS_REC|MS_SLAVE, NULL) = 0
umount2("/sys", MNT_DETACH)             = 0
mount("test", "/sys", "sysfs", 0, NULL) = 0
open("/etc/netns/test", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 5
mount("/etc/netns/test/resolv.conf", "/etc/resolv.conf", 0x55f2f4c2584f, MS_BIND, NULL) = 0

so to reproduce (partially, eg /sys isn't handled here) what ip netns exec test ... is doing:

~# ip netns id

~# head -1 /etc/resolv.conf 
# Generated by NetworkManager

~# nsenter --net=/var/run/netns/test unshare --mount sh -c 'mount --bind /etc/netns/test/resolv.conf /etc/resolv.conf; exec bash'

~# ip netns id
test
~# head -1 /etc/resolv.conf 
# For namespace test
~#

So that's right. nsenter alone isn't enough. unshare has to be used, to change to a newly created mount namespace (basing this new on a copy of the previous one) and alter it, and not just using verbatim an existing one, since there is no existing one yet that fits. That's what is doing the syscall of the same name as is telling strace.