Building a Complete Virtual Network
So far, we've learned the individual building blocks of Linux virtual networking: network namespaces, virtual Ethernet (veth) pairs, and Linux bridges. In this chapter, we'll combine them to build a complete virtual Ethernet network from scratch. By the end, you'll have a network that behaves just like a small physical LAN—all running inside a single Linux kernel.
Learning Objectives
By the end of this chapter, you will be able to:
- Build a complete virtual Layer 2 network from scratch
- Connect multiple network namespaces using a Linux bridge
- Verify connectivity using common Linux networking tools
- Observe ARP, MAC learning, and ICMP traffic
- Understand how containers are connected to host networks
- Troubleshoot common virtual networking problems
What We're Building
Throughout this chapter, we'll build the following topology.
Linux Host
+-------------+
| br0 |
+------+------+
|
+----------------+----------------+
| | |
host1 host2 host3
| | |
================ ================ ================
veth1 veth2 veth3
| | |
Namespace A Namespace B Namespace C
Each namespace will behave like an independent Linux machine.
Each machine will:
- have its own network interface
- have its own IP address
- communicate through a software Ethernet switch
- exchange ARP and ICMP packets just like physical hosts
Nothing leaves the Linux kernel.
Step 1 — Create the Network Namespaces
Create three namespaces.
sudo ip netns add ns1
sudo ip netns add ns2
sudo ip netns add ns3
Verify:
ip netns list
Example:
ns1
ns2
ns3
Enable loopback inside each namespace.
sudo ip netns exec ns1 ip link set lo up
sudo ip netns exec ns2 ip link set lo up
sudo ip netns exec ns3 ip link set lo up
At this point, each namespace contains only a loopback interface.
Step 2 — Create the Linux Bridge
Create a software switch.
sudo ip link add br0 type bridge
Bring it online.
sudo ip link set br0 up
Verify:
ip link show br0
Step 3 — Create Three veth Pairs
Each namespace needs one Ethernet cable.
Create three veth pairs.
sudo ip link add ns1-veth type veth peer name ns1-host
sudo ip link add ns2-veth type veth peer name ns2-host
sudo ip link add ns3-veth type veth peer name ns3-host
Current topology:
ns1-veth <------> ns1-host
ns2-veth <------> ns2-host
ns3-veth <------> ns3-host
All interfaces currently belong to the initial namespace.
Step 4 — Move Interfaces into the Namespaces
Move one end of each pair.
sudo ip link set ns1-veth netns ns1
sudo ip link set ns2-veth netns ns2
sudo ip link set ns3-veth netns ns3
Now the topology becomes:
Namespace ns1
ns1-veth
Host
ns1-host
Repeat for the remaining namespaces.
Step 5 — Connect the Host Interfaces to the Bridge
Attach every host-side interface to the bridge.
sudo ip link set ns1-host master br0
sudo ip link set ns2-host master br0
sudo ip link set ns3-host master br0
The bridge now has three ports.
Verify:
bridge link
Example:
ns1-host
ns2-host
ns3-host
Step 6 — Bring Every Interface Up
Host-side interfaces:
sudo ip link set ns1-host up
sudo ip link set ns2-host up
sudo ip link set ns3-host up
Namespace interfaces:
sudo ip netns exec ns1 ip link set ns1-veth up
sudo ip netns exec ns2 ip link set ns2-veth up
sudo ip netns exec ns3 ip link set ns3-veth up
Every cable is now connected.
Step 7 — Assign IP Addresses
Configure the namespace interfaces.
Namespace 1:
sudo ip netns exec ns1 ip addr add 192.168.50.11/24 dev ns1-veth
Namespace 2:
sudo ip netns exec ns2 ip addr add 192.168.50.12/24 dev ns2-veth
Namespace 3:
sudo ip netns exec ns3 ip addr add 192.168.50.13/24 dev ns3-veth
The network now looks like this.
br0
+--------------+--------------+
ns1-host ns2-host ns3-host
| | |
================ ================ ================
ns1-veth ns2-veth ns3-veth
192.168.50.11 192.168.50.12 192.168.50.13
Step 8 — Verify the Configuration
Check interfaces inside Namespace 1.
sudo ip netns exec ns1 ip addr
Check routes.
sudo ip netns exec ns1 ip route
Output:
192.168.50.0/24 dev ns1-veth
Repeat for the remaining namespaces.
Step 9 — Test Connectivity
From Namespace 1:
sudo ip netns exec ns1 ping 192.168.50.12
Then:
sudo ip netns exec ns1 ping 192.168.50.13
Everything should respond successfully.
Repeat from the other namespaces.
At this point, we have built a fully functional Ethernet LAN.
Understanding the First Ping
Let's examine what happens when Namespace 1 pings Namespace 2.
ping 192.168.50.12
The following sequence occurs.
Application
↓
ICMP Echo Request
↓
IP
↓
Ethernet
↓
ns1-veth
↓
ns1-host
↓
Linux Bridge
↓
ns2-host
↓
ns2-veth
↓
Namespace 2
The bridge forwards the Ethernet frame exactly like a physical switch.
Step 10 — Observe the ARP Process
Clear the neighbor table.
sudo ip netns exec ns1 ip neigh flush all
Verify:
sudo ip netns exec ns1 ip neigh
Output:
<no output>
Now send one ping.
sudo ip netns exec ns1 ping -c1 192.168.50.12
Display the neighbor table again.
sudo ip netns exec ns1 ip neigh
Example:
192.168.50.12 dev ns1-veth lladdr 1a:3b:62:8d:91:ef REACHABLE
ARP behaved exactly as it would on a physical Ethernet network.
Step 11 — Observe the Bridge Learning MAC Addresses
Display the forwarding database.
bridge fdb show br br0
Example:
8a:1d:2e:3f:44:55 dev ns1-host
3e:81:90:71:ac:29 dev ns2-host
72:b0:f4:18:5d:90 dev ns3-host
The bridge learned which MAC addresses are reachable through each bridge port.
No manual configuration was required.
Step 12 — Capture Traffic
Open a terminal.
sudo tcpdump -i ns1-host
Open another terminal.
sudo ip netns exec ns1 ping 192.168.50.12
Observe:
- ARP Request
- ARP Reply
- ICMP Echo Request
- ICMP Echo Reply
The packets are identical to those seen on a physical Ethernet network.
Step 13 — Test Broadcast Traffic
Send an ARP request again.
sudo ip netns exec ns1 ping -c1 192.168.50.13
The ARP request is broadcast.
The bridge floods it to every port except the incoming one.
br0
+------+------+------+
ns1 ns2 ns3
Broadcast Frame
Namespace 2 ignores the request.
Namespace 3 responds because the IP address belongs to it.
Step 14 — Add the Host to the Network
The bridge itself is a network interface.
Assign it an IP address.
sudo ip addr add 192.168.50.1/24 dev br0
Now the host can communicate with every namespace.
Test from the host.
ping 192.168.50.11
Or from Namespace 1.
sudo ip netns exec ns1 ping 192.168.50.1
The host is now another machine on the same Ethernet segment.
A Familiar Architecture
If this topology looks familiar, that's because it is.
Docker's default bridge network is conceptually very similar.
docker0
+---------+---------+
veth veth
| |
Container Container
KVM, libvirt, LXC, and many virtualization platforms build similar topologies using the same Linux networking primitives.
Troubleshooting the Network
When the network doesn't work, check the following in order.
Is the namespace present?
ip netns list
Are interfaces up?
Host:
ip link
Namespace:
ip netns exec ns1 ip link
Look for:
UP
LOWER_UP
Is the interface connected to the bridge?
bridge link
Does the namespace have an IP address?
ip netns exec ns1 ip addr
Does the namespace have the expected route?
ip netns exec ns1 ip route
Is ARP working?
ip netns exec ns1 ip neigh
Did the bridge learn MAC addresses?
bridge fdb show
Can you see packets?
tcpdump -i ns1-host
or
ip netns exec ns1 tcpdump -i ns1-veth
Cleaning Up
Delete the namespaces.
sudo ip netns delete ns1
sudo ip netns delete ns2
sudo ip netns delete ns3
Delete the bridge.
sudo ip link delete br0
The remaining host-side veth interfaces disappear automatically because their peers no longer exist.
What We've Built
Without Docker...
Without Kubernetes...
Without libvirt...
Without any container runtime...
...we built an entire virtual Ethernet network using only Linux kernel primitives.
Specifically, we combined:
- Network namespaces for isolation
- veth pairs for point-to-point links
- A Linux bridge for Layer 2 switching
This is the foundation upon which many modern container and virtualization platforms are built.
Key Takeaways
- A complete virtual Ethernet network can be built using only network namespaces, veth pairs, and a Linux bridge.
- Each namespace behaves like an independent Linux host with its own interfaces, routing table, neighbor table, and sockets.
- A Linux bridge forwards Ethernet frames between connected interfaces using MAC address learning.
- ARP, ICMP, and Ethernet behave exactly as they do on a physical network.
- The bridge itself can be assigned an IP address, allowing the host to participate in the virtual network.
- The architecture built in this chapter closely resembles the default networking model used by Docker, libvirt, and many other virtualization platforms.
Coming Next
The virtual network we built behaves like a single Layer 2 Ethernet segment. Every namespace belongs to the same IP subnet, so they can communicate directly without a router.
In the next chapter, we'll transform the Linux host into a router. We'll create multiple independent networks, enable IP forwarding, and learn how Linux routes packets between subnets—the same principles used by physical routers, cloud gateways, and Kubernetes nodes.