Network namespaces change that fundamental assumption. With network namespaces, you can have different and separate instances of network interfaces and routing tables that operate independent of each other. Let's see how it works, using the Python package Pyroute2:
>>> from pyroute2 import IPDB, IPRoute, NetNS, netns, NSPopen
>>> from subprocess import Popen, PIPE
>>> ipdb_namespace1 = IPDB(nl=NetNS('namespace1'))
>>> nsp = NSPopen('namespace1', ['ip', 'addr'], stdout=PIPE)
>>> for l in nsp.communicate()[0].split(b'\n'):
... print(l)
...
b'1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default '
b' link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00'
b''
We've just created a network namespace named namespace1
. The ip addr
command launched inside the namespace namespace1
tells us that there is only one interface, lo
.
Now, we need a way to link our newly created namespace, to the root namespace
. To do that, virtual ethernet devices, veth pair
, need to be created and configured. A veth pair
works like a patch cable, connecting two sides. It consists of two virtual interfaces, one of them is assigned to the root network namespace, while the other lives within the network namespace, namespace1
:
>>> ipdb_root = IPDB() # root namespace
>>> ipdb_root.create(ifname='v01', kind='veth', peer='vp01').commit()
{'linkmode': 0, 'broadcast': 'ff:ff:ff:ff:ff:ff', 'num_rx_queues': 1, 'qdisc': 'noop', 'txqlen': 1000, 'ipaddr': [], 'group': 0, 'flags': 4098, 'neighbours': [], 'mtu': 1500, 'promiscuity': 0, 'vlans': [], 'address': 'ca:a0:d7:39:57:22', 'ipdb_scope': 'system', 'kind': 'veth', 'ipdb_priority': 0, 'ifi_type': 1, 'family': 0, 'carrier': 0, 'ports': [], 'peer': 'vp01', 'num_tx_queues': 1, 'index': 10, 'ifname': 'v01', 'carrier_changes': 1, 'operstate': 'DOWN'}
>>>
Veth pair device created. Both sides of the veth pair device are actually in the root namespace
. To link the root namespace
to our newly created namespace namespace1
, one side of the veth pair device, the peer, should be assigned to namespace1
:
>>> with ipdb_root.interfaces.vp01 as v:
... v.net_ns_fd = 'namespace1'
...
>>>
Now, let's give IP addresses to all of our interfaces, and set everybody up:
>>> with ipdb_root.interfaces.v01 as veth:
... veth.add_ip('192.168.1.36/24')
... veth.up()
...
{'kind': 'veth', 'promiscuity': 0, 'group': 0, 'ipdb_scope': 'system', 'peer': 'vp01', 'linkmode': 0, 'ipdb_priority': 0, 'carrier_changes': 1, 'address': 'f2:2d:aa:9d:16:da', 'mtu': 1500, 'carrier': 0, 'ifname': 'v01', 'num_tx_queues': 1, 'index': 6, 'ifi_type': 1, 'ports': [], 'vlans': [], 'ipaddr': [], 'qdisc': 'noop', 'neighbours': [], 'txqlen': 1000, 'broadcast': 'ff:ff:ff:ff:ff:ff', 'operstate': 'DOWN', 'num_rx_queues': 1, 'flags': 4098, 'family': 0}
>>>
>>> with ipdb_namespace1.interfaces.vp01 as veth:
... veth.add_ip('192.168.1.37/24')
... veth.up()
...
{'kind': 'veth', 'promiscuity': 0, 'group': 0, 'ipdb_scope': 'system', 'ifi_type': 1, 'linkmode': 0, 'ipdb_priority': 0, 'carrier_changes': 1, 'num_rx_queues': 1, 'address': '26:29:f4:06:8a:ea', 'mtu': 1500, 'carrier': 0, 'ifname': 'vp01', 'num_tx_queues': 1, 'index': 5, 'ports': [], 'vlans': [], 'ipaddr': [], 'qdisc': 'noop', 'neighbours': [], 'txqlen': 1000, 'broadcast': 'ff:ff:ff:ff:ff:ff', 'operstate': 'DOWN', 'flags': 4098, 'family': 0}
>>>
>>> with ipdb_namespace1.interfaces.lo as i:
... i.up()
...
{'flags': 8, 'promiscuity': 0, 'group': 0, 'ipdb_scope': 'system', 'ifi_type': 772, 'linkmode': 0, 'ipdb_priority': 0, 'carrier_changes': 0, 'address': '00:00:00:00:00:00', 'mtu': 65536, 'carrier': 1, 'ifname': 'lo', 'num_tx_queues': 1, 'index': 1, 'ports': [], 'vlans': [], 'ipaddr': [], 'qdisc': 'noop', 'neighbours': [], 'txqlen': 0, 'broadcast': '00:00:00:00:00:00', 'operstate': 'DOWN', 'num_rx_queues': 1, 'family': 0}
>>>
At this point, we can open a shell and directly check the configuration for v01
interface:
kintanu:~# ip addr show v01
6: v01: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether f2:2d:aa:9d:16:da brd ff:ff:ff:ff:ff:ff
inet 192.168.1.36/24 scope global v01
valid_lft forever preferred_lft forever
inet6 fe80::f02d:aaff:fe9d:16da/64 scope link
valid_lft forever preferred_lft forever
We can also, still from the shell, check the available interfaces inside namespace1
:
kintanu:~# ip netns exec namespace1 ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
5: vp01: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 26:29:f4:06:8a:ea brd ff:ff:ff:ff:ff:ff
inet 192.168.1.37/24 scope global vpeer01
valid_lft forever preferred_lft forever
inet6 fe80::2429:f4ff:fe06:8aea/64 scope link
valid_lft forever preferred_lft forever
kintanu:~#
A system uses its routing table to determine which network interface to use when sending packets to remote systems. Let's make all traffic leaving namespace1
to go through v01
:
kintanu:~# ip netns exec namespace1 ip route add default via 192.168.1.36
I told you I often use LXC containers when I'm working. For that reason, I always have a bridge configured and up. Then, all the containers are connected to the internet, via the bridge, as ports
. Let me show you:
kintanu:~# ip addr show br0
4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 3c:97:0e:65:c0:27 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.34/24 brd 192.168.1.255 scope global br0
valid_lft forever preferred_lft forever
inet6 fe80::3e97:eff:fe65:c027/64 scope link
valid_lft forever preferred_lft forever
kintanu:~# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.3c970e65c027 no eth0
vethLMRO1N
kintanu:~#
The br0
interface is the interface I use for going outside. In fact, br0
is a bridge. eth0
and vethLMRON
are interfaces, ports
, attached to that bridge. v01
should be attached to the br0
bridge, otherwise, v01
won't be able to send packets to the outside world:
kintanu:~# brctl addif br0 v01
kintanu:~# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.3c970e65c027 no eth0
vethLMRO1N
v01
kintanu:~#
We've just seen how to create a network namespace, and link that network namespace to the outside world. When playing with LXC containers, you will also heard about control groups
, a kernel mechanism that will allow you to allocate resources among user-defined groups of tasks (processes) running on a system. That will be the topic of the next chapter.
All the diagrams were made using [Asciiflow](http://asciiflow.com/)