Network Namespace

So far in this series, we’ve looked at isolating processes in PID, mount and UTS namespaces. The next namespace in this sequence is the network namespace, which allows you to isolate a process in terms of its network stack. That is, a process that is cast into a new network namespace using one of the system calls clone or unshare, or an existing network namespace with setns, are isolated from the host’s network stack, but enjoy their own private network stack associated with the namespace. The resources that are isolated include routes, firewall rules, network devices, and ports.

We’ve been developing a program to demonstrate how each of the namespaces work, and the changes that incorporate the network namespace can be found in invoke_ns4.c, which is available here. The changes that have been made from the last iteration are trivial; we’ve added a new command line option, -n, which specifies that the cloned process is to be created in a new network namespace, and the addition of the CLONE_NEWNET constant to the clone flags when parsing the command line options:

// Parse command line options and construct arguments
// to be passed to childFunction
while ((option = getopt(argc, argv, "+hvpmu:n")) != -1) {
    switch (option) {
        case 'n':
            flags |= CLONE_NEWNET;
            break;
        case 'u':
            flags |= CLONE_NEWUTS;
            args.hostname = malloc(sizeof(char) * (strlen(optarg) + 1));
            strcpy(args.hostname, optarg);
            break;
        case 'm':
            flags |= CLONE_NEWNS;
            break;
        case 'p':
            flags |= CLONE_NEWPID;
            break;
        case 'v':
            args.verbose = 1;
            break;
        case 'h':
            usage(argv[0]);
            exit(EXIT_SUCCESS);
        default:
            usage(argv[0]);
            exit(EXIT_FAILURE);
    }
}

Once the new version of the program has been compiled, we can invoke it with the following command line options and arguments:

$ sudo ./invoke_ns -vpmnu calculus env PS1="\h\[\] [\[\]\w\[\]] " PATH=$PATH bash --norc
[sudo] password for wolf:
Parent: PID of parent is 6142
Parent: PID of child is 6143
 Child: PID of child is 1
 Child: Executing command bash ...
calculus [/home/wolf]

The cloned process is created in a number of namespaces, including a new network namespace. Just as we’ve done in the previous examples, we can confirm that the process resides in its own network namespace, distinct from the global network namespace of all the other processes running on the system:

$ sudo ls -l /proc/6142/ns && sudo ls -l /proc/6143/ns
[sudo] password for wolf:
total 0
lrwxrwxrwx 1 root root 0 Mar 12 12:21 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 uts -> uts:[4026531838]
total 0
lrwxrwxrwx 1 root root 0 Mar 12 12:21 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 mnt -> mnt:[4026532430]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 net -> net:[4026532434]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 pid -> pid:[4026532432]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 12 12:21 uts -> uts:[4026532431]

At the bash command prompt of the cloned process, we can see what the new network namespace provides the process:

calculus [/home/wolf] ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

The kernel has simply provided a loopback interface, which we can bring up with ip link set lo up.

This is very spartan, and it is up to the initiator to configure a meaningful network stack for the namespace. Assuming that we want any processes running in the new network namespace to be able to communicate beyond the boundaries of the namespace, then we need to establish a ’tunnel’ between the global network namespace and our newly created network namespace.

To do this, we can create a pair of virtual ethernet interfaces (veth pair), one which will reside in the new network namespace, and one that will reside in the global network namespace. The pair of interfaces are linked to one another like a pipe, and whatever appears at one end, finds its way to the other end.

The veth pair is created in the global network namespace, and then we need to move one of the interfaces into our new network namespace. We’ll call the interface that will reside in the global network namespace, ‘host’, whilst the interface that is to be moved to the new network namespace will be called ‘guest’. In a bash command shell which resides in the global network namespace, we use the following to create the veth pair:

$ sudo ip link add host type veth peer name guest

When this is done, we can see the newly created veth pair by listing the interfaces available in the global network namespace:

$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
    link/ether 08:00:27:fd:d7:6a brd ff:ff:ff:ff:ff:ff
3: [email protected]: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 4a:47:28:07:d4:d6 brd ff:ff:ff:ff:ff:ff
4: [email protected]: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether de:7b:af:36:05:e3 brd ff:ff:ff:ff:ff:ff

In order to move the ‘guest’ interface into the new network namespace, we need to use the PID of the cloned child process, as it is in the global network namespace, that is 6143. The command simply moves the interface called ‘guest’ into the network namespace which PID 6143 resides in:

$ sudo ip link set guest netns 6143

We can check that this has been accomplished by issuing the ip link show command in both network namespaces. In the global network namespace:

$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
    link/ether 08:00:27:fd:d7:6a brd ff:ff:ff:ff:ff:ff
4: [email protected]: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether de:7b:af:36:05:e3 brd ff:ff:ff:ff:ff:ff link-netnsid 0

And in the new network namespace:

calculus [/home/wolf] ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: [email protected]: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 4a:47:28:07:d4:d6 brd ff:ff:ff:ff:ff:ff link-netnsid 0

We can assign an IP address to each of the interfaces in the veth pair. In the new network namespace, we can execute the following:

calculus [/home/wolf] ip addr add 10.0.0.2/24 dev guest
calculus [/home/wolf] ip link set guest up
calculus [/home/wolf] ip addr show guest
3: [email protected]: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000
    link/ether 4a:47:28:07:d4:d6 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.0.0.2/24 scope global guest
       valid_lft forever preferred_lft forever

And in the global network namespace:

$ sudo ip addr add 10.0.0.1/24 dev host
[[email protected] ~]$ sudo ip link set host up
[[email protected] ~]$ ip addr show host
4: [email protected]: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether de:7b:af:36:05:e3 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.0.0.1/24 scope global host
       valid_lft forever preferred_lft forever
    inet6 fe80::dc7b:afff:fe36:5e3/64 scope link 
       valid_lft forever preferred_lft forever

Finally, we can check that each interface is reachable from the other by pinging from each end.

In order to make a network namespace more useful, additional work would need to be carried out to establish a suitable network configuration for a specific purpose, e.g. creating a bridge on the host for bridging to a physical network interface.

In the next article, we’ll take a look at the IPC namespace.


Additional notes on network namespaces

Physical network interfaces can only exist in one network namespace at a time, and are returned to the global network namespace as and when the network namespace they reside in, terminates.