So far in this series, we've looked at isolating processes in PID, MNT and UTS namespaces. The next namespace in this sequence is the NET namespace, which allows you to isolate a process in terms of its network stack. That is, a process that is cast into a new NET namespace using one of the system calls clone or unshare, or an existing NET 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 NET namespace can be found in invoke_ns4.c, which is available here. The changes that have 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 NET 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 bash
[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 [~]  

The cloned process is created in a number of namespaces, including a new NET namespace. Just as we've done in the previous examples, we can confirm that the process resides in its own NET namespace, distinct from the global NET 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 NET namespace provides the process:

calculus [~] ip link show  
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default  
    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 NET namespace to be able to communicate beyond the boundaries of the namespace, then we need to establish a 'tunnel' between the global NET namespace and our newly created NET namespace.

To do this, we can create a pair of virtual ethernet interfaces (veth pair), one which will reside in the new NET namespace, and one that will reside in the global NET 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 NET namespace, and then we need to move one of the interfaces into our new NET namespace. We'll call the interface that will reside in the global NET namespace, 'host', whilst the interface that is to be moved to the new NET namespace will be called 'guest'. In a bash command shell which resides in the global NET 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 NET namespace:

$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default  
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000  
    link/ether 00:24:e8:f3:b1:48 brd ff:ff:ff:ff:ff:ff
3: guest: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000  
    link/ether 9a:c9:3c:16:ef:6b brd ff:ff:ff:ff:ff:ff
4: host: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000  
    link/ether ba:10:71:4e:81:01 brd ff:ff:ff:ff:ff:ff

In order to move the 'guest' interface into the new NET namespace, we need to use the PID of the cloned child process, as it is in the global NET namespace, that is 6143. The command simply moves the interface called 'guest' into the NET 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 NET namespaces. In the global NET namespace:

$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default  
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000  
    link/ether 00:24:e8:f3:b1:48 brd ff:ff:ff:ff:ff:ff
4: host: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000  
    link/ether ba:10:71:4e:81:01 brd ff:ff:ff:ff:ff:ff

And in the new NET namespace:

calculus [~] ip link show  
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default  
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: guest: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000  
    link/ether 9a:c9:3c:16:ef:6b brd ff:ff:ff:ff:ff:ff

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

calculus [~] ip addr add 10.0.0.2/24 dev guest  
calculus [~] ip link set guest up  
calculus [~] ip addr show guest  
3: guest: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000  
    link/ether 9a:c9:3c:16:ef:6b brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.2/24 scope global guest
       valid_lft forever preferred_lft forever
    inet6 fe80::98c9:3cff:fe16:ef6b/64 scope link
       valid_lft forever preferred_lft forever

And in the global NET namespace:

$ sudo ip addr add 10.0.0.1/24 dev host
$ sudo ip link set host up
$ ip addr show host
4: host: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000  
    link/ether ba:10:71:4e:81:01 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.1/24 scope global host
       valid_lft forever preferred_lft forever
    inet6 fe80::b810:71ff:fe4e:8101/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 NET 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.


Additional notes on NET namespaces

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