A feature of the Linux kernel called namespace isolation [1] defines groups of separated processes such that each process cannot "see" resources in the other groups. This is a form of lightweight process virtualization used in some container implementations such as LXC.
There are currently six namespaces implementations:
- mnt: mount points and filesystems isolation
- pid: process isolation
- net: network stack isolation
- ipc: System V IPC isolation
- uts: hostname isolation
- user: user isolation by means of UIDs
In this post I'll show few examples of how to create network namespaces and use them with Open vSwitch.
A network namespace is a separate copy of the network stack - it contains its own routes, network devices and iptables rules. It is defined in include/net/net_namespace.h where each device belongs to only one network namespace.
There's always the default network namespace, called the root namespace where all network interfaces are assigned to:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip link
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 bc:76:4e:11:b5:87 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether bc:76:4e:11:b7:85 brd ff:ff:ff:ff:ff:ff
root@ovs-tests:~# ip a s
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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether bc:76:4e:11:b5:87 brd ff:ff:ff:ff:ff:ff
inet 104.130.212.227/24 brd 104.130.212.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 2001:4801:7827:101:be76:4eff:fe11:b587/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::be76:4eff:fe11:b587/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether bc:76:4e:11:b7:85 brd ff:ff:ff:ff:ff:ff
inet 10.210.102.116/19 brd 10.210.127.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::be76:4eff:fe11:b785/64 scope link
valid_lft forever preferred_lft forever
root@ovs-tests:~# ip r
default via 104.130.212.1 dev eth0
10.176.0.0/12 via 10.210.96.1 dev eth1
10.208.0.0/12 via 10.210.96.1 dev eth1
10.210.96.0/19 dev eth1 proto kernel scope link src 10.210.102.116
104.130.212.0/24 dev eth0 proto kernel scope link src 104.130.212.227
In this case you can see 3 network interfaces - lo, eth0 and eth1.
Now lets create 2 network namespaces called ns1 and ns2 and list them:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip netns add ns1
root@ovs-tests:~# ip netns add ns2
root@ovs-tests:~# ip netns
ns2
ns1
root@ovs-tests:~# ls -la /var/run/netns/
total 0
drwxr-xr-x 2 root root 80 Jul 20 14:54 .
drwxr-xr-x 19 root root 600 Jul 20 14:54 ..
-r--r--r-- 1 root root 0 Jul 20 14:54 ns1
-r--r--r-- 1 root root 0 Jul 20 14:54 ns2
root@ovs-tests:~#
To execute a command inside the namespace:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip netns exec ns1 ip link
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
root@ovs-tests:~# ip netns exec ns1 bash
root@ovs-tests:~# ip link
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
root@ovs-tests:~# exit
exit
root@ovs-tests:~#
We can use "ip netns exec ns1
command " where command is either the actual command we want to execute or bash, which puts us in the namespace's shell, where we can run commands as usual without the need of prefixing them with "ip netns exec ns1".
As you can see the ns1 namespace only contains the loopback interface in a DOWN state. The same is true for the ns2 network namespace.
To connect the two lets first create a software switch using Open vSwitch:
File: gistfile1.txt
-------------------
root@ovs-tests:~# apt-get update && apt-get install openvswitch-switch
root@ovs-tests:~# ovs-vsctl add-br OVS-1
root@ovs-tests:~# ovs-vsctl show
cad47b00-2d1d-4149-9031-d369592d6d88
Bridge "OVS-1"
Port "OVS-1"
Interface "OVS-1"
type: internal
ovs_version: "2.3.0"
root@ovs-tests:~# ip a s OVS-1
5: OVS-1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default
link/ether c6:e3:a0:8b:47:49 brd ff:ff:ff:ff:ff:ff
Lets create a link between the namespaces ns1 and ns2 through the virtual switch by specifying the two ends of the connection, called a virtual pair.
To create the two pairs run:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip link add eth1-ns1 type veth peer name veth-ns1
root@ovs-tests:~# ip link add eth1-ns2 type veth peer name veth-ns2
root@ovs-tests:~# ip link
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 bc:76:4e:11:b5:87 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether bc:76:4e:11:b7:85 brd ff:ff:ff:ff:ff:ff
4: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default
link/ether 36:ff:6e:ca:01:ae brd ff:ff:ff:ff:ff:ff
5: OVS-1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default
link/ether c6:e3:a0:8b:47:49 brd ff:ff:ff:ff:ff:ff
6: veth-ns1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 2e:eb:33:7b:62:86 brd ff:ff:ff:ff:ff:ff
7: eth1-ns1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether de:0c:5f:c2:1c:0d brd ff:ff:ff:ff:ff:ff
8: veth-ns2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 56:6e:02:21:15:1f brd ff:ff:ff:ff:ff:ff
9: eth1-ns2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether b6:e3:94:fe:be:23 brd ff:ff:ff:ff:ff:ff
The commands above created two virtual links in the root namespace, each having two interface on each end - eth1-ns1/veth-ns1 and eth1-ns2/veth-ns2. Think of the eth1-ns1 and veth-ns1 interfaces as two opposite ends of the same pipe. The names are arbitrary.
First lets connect one end of the virtual connections to each name space:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip link set eth1-ns1 netns ns1
root@ovs-tests:~# ip netns exec ns1 ip link
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
7: eth1-ns1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether de:0c:5f:c2:1c:0d brd ff:ff:ff:ff:ff:ff
root@ovs-tests:~#
root@ovs-tests:~# ip link set eth1-ns2 netns ns2
root@ovs-tests:~# ip netns exec ns2 ip link
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
9: eth1-ns2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether b6:e3:94:fe:be:23 brd ff:ff:ff:ff:ff:ff
root@ovs-tests:~#
Notice how the eth1-ns1 and eth1-ns2 interfaces no longer show in the root name space, but in their newly assigned ns1 and ns2 namespaces.
Then lets connect the other end to a port on the OVS:
File: gistfile1.sh
------------------
root@ovs-tests:~# ovs-vsctl add-port OVS-1 veth-ns1
root@ovs-tests:~# ovs-vsctl add-port OVS-1 veth-ns2
root@ovs-tests:~# ovs-vsctl show
cad47b00-2d1d-4149-9031-d369592d6d88
Bridge "OVS-1"
Port "veth-ns2"
Interface "veth-ns2"
Port "OVS-1"
Interface "OVS-1"
type: internal
Port "veth-ns1"
Interface "veth-ns1"
ovs_version: "2.3.0"
root@ovs-tests:~#
The end result is two network namespaces ns1 and ns2 each having eth1-ns1/2 interface connected to an OVS ports veth-ns1/2. Let's bring the interfaces up and assign IPs.
In the root namespace lets bring the veth-ns1/2 interfaces up:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip link set veth-ns1 up
root@ovs-tests:~# ip link set veth-ns2 up
root@ovs-tests:~# ip link
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 bc:76:4e:11:b5:87 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether bc:76:4e:11:b7:85 brd ff:ff:ff:ff:ff:ff
4: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default
link/ether 36:ff:6e:ca:01:ae brd ff:ff:ff:ff:ff:ff
5: OVS-1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default
link/ether c6:e3:a0:8b:47:49 brd ff:ff:ff:ff:ff:ff
6: veth-ns1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast master ovs-system state DOWN mode DEFAULT group default qlen 1000
link/ether 2e:eb:33:7b:62:86 brd ff:ff:ff:ff:ff:ff
8: veth-ns2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast master ovs-system state DOWN mode DEFAULT group default qlen 1000
link/ether 56:6e:02:21:15:1f brd ff:ff:ff:ff:ff:ff
root@ovs-tests:~#
Then in each network namespace ns1 and ns2 lets bring the lo and eth-ns1/2 interfaces up and assign IPs:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip netns exec ns1 ip link set dev lo up
root@ovs-tests:~# ip netns exec ns1 ip link set dev eth1-ns1 up
root@ovs-tests:~# ip netns exec ns1 ip address add 192.168.0.1/24 dev eth1-ns1
root@ovs-tests:~# ip netns exec ns1 ip a s
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
7: eth1-ns1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether de:0c:5f:c2:1c:0d brd ff:ff:ff:ff:ff:ff
inet 192.168.0.1/24 scope global eth1-ns1
valid_lft forever preferred_lft forever
inet6 fe80::dc0c:5fff:fec2:1c0d/64 scope link
valid_lft forever preferred_lft forever
root@ovs-tests:~#
root@ovs-tests:~# ip netns exec ns2 ip link set dev lo up
root@ovs-tests:~# ip netns exec ns2 ip link set dev eth1-ns2 up
root@ovs-tests:~# ip netns exec ns2 ip address add 192.168.0.2/24 dev eth1-ns2
root@ovs-tests:~# ip netns exec ns2 ip a s
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
9: eth1-ns2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether b6:e3:94:fe:be:23 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.2/24 scope global eth1-ns2
valid_lft forever preferred_lft forever
inet6 fe80::b4e3:94ff:fefe:be23/64 scope link
valid_lft forever preferred_lft forever
root@ovs-tests:~#
We should now have network connectivity between ns1 and ns2. Let's verify:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip netns exec ns1 ping -c 3 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.237 ms
64 bytes from 192.168.0.2: icmp_seq=2 ttl=64 time=0.066 ms
64 bytes from 192.168.0.2: icmp_seq=3 ttl=64 time=0.059 ms
--- 192.168.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.059/0.120/0.237/0.083 ms
root@ovs-tests:~# ip netns exec ns2 ping -c 3 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=0.215 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=0.051 ms
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=0.061 ms
--- 192.168.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.051/0.109/0.215/0.075 ms
root@ovs-tests:~#
Now lets start a dnsmasq server in ns1 and try to get an IP lease from ns2:
File: gistfile1.sh
------------------
root@ovs-tests:~# ip netns exec ns1 bash
root@ovs-tests:~# ip a s
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
7: eth1-ns1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether de:0c:5f:c2:1c:0d brd ff:ff:ff:ff:ff:ff
inet 192.168.0.1/24 scope global eth1-ns1
valid_lft forever preferred_lft forever
inet6 fe80::dc0c:5fff:fec2:1c0d/64 scope link
valid_lft forever preferred_lft forever
root@ovs-tests:~# dnsmasq --interface=eth1-ns1 --dhcp-range=192.168.0.100,192.168.0.200,255.255.255.0
root@ovs-tests:~# pgrep -lf dnsmasq
13219 dnsmasq
root@ovs-tests:~# exit
exit
root@ovs-tests:~# ip netns exec ns2 bash
root@ovs-tests:~# dhclient -r
Killed old client process
root@ovs-tests:~# ip a s
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
9: eth1-ns2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether b6:e3:94:fe:be:23 brd ff:ff:ff:ff:ff:ff
inet6 fe80::b4e3:94ff:fefe:be23/64 scope link
valid_lft forever preferred_lft forever
root@ovs-tests:~# dhclient eth1-ns2
root@ovs-tests:~# ip a s
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
9: eth1-ns2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether b6:e3:94:fe:be:23 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.195/24 brd 192.168.0.255 scope global eth1-ns2
valid_lft forever preferred_lft forever
inet6 fe80::b4e3:94ff:fefe:be23/64 scope link
valid_lft forever preferred_lft forever
root@ovs-tests:~# exit
exit
root@ovs-tests:~#
The logs also show that ns2 got a new IP lease from the ns1 dnsmasq process:
File: gistfile1.sh
------------------
root@ovs-tests:~# tail -f /var/log/syslog
Jul 20 16:25:31 localhost dnsmasq[13219]: started, version 2.72 cachesize 150
Jul 20 16:25:31 localhost dnsmasq[13219]: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect
Jul 20 16:25:31 localhost dnsmasq-dhcp[13219]: DHCP, IP range 192.168.0.100 -- 192.168.0.200, lease time 1h
Jul 20 16:25:31 localhost dnsmasq[13219]: reading /etc/resolv.conf
Jul 20 16:25:31 localhost dnsmasq[13219]: using nameserver 173.203.4.9#53
Jul 20 16:25:31 localhost dnsmasq[13219]: using nameserver 173.203.4.8#53
Jul 20 16:25:31 localhost dnsmasq[13219]: ignoring nameserver 192.168.0.1 - local interface
Jul 20 16:25:31 localhost dnsmasq[13219]: read /etc/hosts - 4 addresses
Jul 20 16:26:03 localhost dhclient: Killed old client process
Jul 20 16:26:04 localhost dhclient: DHCPRELEASE on eth1-ns2 to 192.168.0.1 port 67
Jul 20 16:26:04 localhost dnsmasq-dhcp[13219]: DHCPRELEASE(eth1-ns1) 192.168.0.195 b6:e3:94:fe:be:23
Jul 20 16:26:25 localhost dhclient: DHCPDISCOVER on eth1-ns2 to 255.255.255.255 port 67 interval 4
Jul 20 16:26:25 localhost dnsmasq[13219]: reading /etc/resolv.conf
Jul 20 16:26:25 localhost dnsmasq[13219]: using nameserver 173.203.4.9#53
Jul 20 16:26:25 localhost dnsmasq[13219]: using nameserver 173.203.4.8#53
Jul 20 16:26:25 localhost dnsmasq-dhcp[13219]: DHCPDISCOVER(eth1-ns1) 192.168.0.195 b6:e3:94:fe:be:23
Jul 20 16:26:25 localhost dnsmasq-dhcp[13219]: DHCPOFFER(eth1-ns1) 192.168.0.195 b6:e3:94:fe:be:23
Jul 20 16:26:25 localhost dhclient: DHCPREQUEST on eth1-ns2 to 255.255.255.255 port 67
Jul 20 16:26:25 localhost dhclient: DHCPOFFER from 192.168.0.1
Jul 20 16:26:25 localhost dnsmasq-dhcp[13219]: DHCPREQUEST(eth1-ns1) 192.168.0.195 b6:e3:94:fe:be:23
Jul 20 16:26:25 localhost dnsmasq-dhcp[13219]: DHCPACK(eth1-ns1) 192.168.0.195 b6:e3:94:fe:be:23 ovs-tests
Jul 20 16:26:28 localhost dhclient: DHCPREQUEST on eth1-ns2 to 255.255.255.255 port 67
Jul 20 16:26:28 localhost dnsmasq-dhcp[13219]: DHCPREQUEST(eth1-ns1) 192.168.0.195 b6:e3:94:fe:be:23
Jul 20 16:26:28 localhost dnsmasq-dhcp[13219]: DHCPACK(eth1-ns1) 192.168.0.195 b6:e3:94:fe:be:23 ovs-tests
Jul 20 16:26:28 localhost dhclient: DHCPACK from 192.168.0.1
Jul 20 16:26:28 localhost dhclient: bound to 192.168.0.195 -- renewal in 1499 seconds.
To isolate both name spaces into their own VLANs:
File: gistfile1.sh
------------------
root@ovs-tests:~# ovs-vsctl set port veth-ns1 tag=100
root@ovs-tests:~# ovs-vsctl set port veth-ns2 tag=200
root@ovs-tests:~# ovs-vsctl show
cad47b00-2d1d-4149-9031-d369592d6d88
Bridge "OVS-1"
Port "veth-ns2"
tag: 200
Interface "veth-ns2"
Port "OVS-1"
Interface "OVS-1"
type: internal
Port "veth-ns1"
tag: 100
Interface "veth-ns1"
ovs_version: "2.3.0"
root@ovs-tests:~#
Now connectivity between both ns1 and ns2 should be lost.
Resources:
[1]. http://man7.org/linux/man-pages/man7/namespaces.7.html