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.