↓ Skip to main content

How Linux Constructs Virtual Networks

1. Veth Pairs (Virtual Ethernet Interfaces)
#

A veth pair acts as a virtual bidirectional Ethernet cable. Packets transmitted on one end.

graph LR
    subgraph Host ["Linux Host"]
        direction LR
        
        subgraph Veth0_Dev ["veth0 Interface"]
            A["veth0
192.168.1.1/24"] end subgraph Veth1_Dev ["veth1 Interface"] B["veth1
192.168.1.2/24"] end A <===> |"Virtual Ethernet Cable (Peer)"| B end

Basic Commands
#

# Create a veth pair named veth0 and veth1
ip link add veth0 type veth peer name veth1

# Assign an IP address to veth0
ip addr add 192.168.1.1/24 dev veth0

# Bring the interfaces up
ip link set veth0 up
ip link set veth1 up

# Verify created devices
ip link show

Crucial Kernel Parameters
#

When testing local veth packet routing on the same host, the kernel’s Reverse Path Filtering (rp_filter) or local interface route checks may silently drop packets.

  • sysctl -w net.ipv4.conf.veth0.rp_filter=0: Disables strict reverse path filtering (RPF) checks.

  • sysctl -w net.ipv4.conf.veth0.accept_local=1: Allows accepting packets with a local source IP.

Inspecting Traffic
#

# Capture packets passing through the virtual interface
tcpdump -nn -i veth0

Veth Kernel Internals & Packet Path
#

Unlike physical network cards that use DMA to push frames to hardware, veth relies on veth_xmit() inside the driver:

  1. Application calls send() –> allocates an sk_buff payload.
  2. veth_xmit() grabs the peer device handle (priv->peer).
  3. Switches the packet context to the peer interface via netif_rx() or netif_receive_skb().
  4. Triggering a softirq (NET_RX_SOFTIRQ) processes the frame as an incoming packet on the peer device.

2. Network Namespaces (netns)
#

Network namespaces isolate network stacks across processes—providing independent routing tables, firewall rules (iptables/nftables), socket lists, ARP tables, and network interfaces (lo, eth0).

Working with Network Namespaces
#

# Create a network namespace named net1
ip netns add net1

# Create a veth pair
ip link add veth-host type veth peer name veth-net1

# Move veth-net1 inside the net1 namespace
ip link set veth-net1 netns net1

# Configure IP inside net1
ip netns exec net1 ip addr add 192.168.1.2/24 dev veth-net1
ip netns exec net1 ip link set veth-net1 up
ip netns exec net1 ip link set lo up

Kernel Representations
#

  • Process to Namespace (task_struct –> nsproxy –> net): A process references its namespace via current->nsproxy->net_ns.
  • The struct net Structure: Contains loopback pointers (loopback_dev), IPv4 protocols (netns_ipv4), sysctl knobs, and active socket tracking tables.

Lifecycle & Creation
#

  • clone() Flag: Passing CLONE_NEWNET to clone() or unshare() provisions a new struct net instance.
  • Subsystem Initialization: Each network subsystem (TCP, UDP, Netfilter, Routing) executes its init_net constructor function inside the new namespace.
  • Sockets and Namespaces: Every socket (struct sock) stores a pointer sk_net referencing its parent struct net. Sockets cannot cross namespace boundaries once bound.

3. Linux Bridge (net_bridge)
#

A Linux Bridge operates as a virtual Layer-2 Software Switch, connecting multiple veth endpoints or physical interfaces into a shared broadcast domain.

Primary Kernel Structures
#

  • struct net_device: Represents the virtual bridge interface (br0).

  • struct net_bridge: Represents the internal bridge state, including port lists, MAC forwarding tables (fdb), and spanning tree parameters.

Packet Traversal in a Bridge
#

  1. An incoming frame arrives at a bridged port via netif_receive_skb().
  2. Execution diverts to br_handle_frame().
  3. The bridge checks its MAC forwarding table (FDB):
    • Unicast Hit: Forwards directly to the target port (br_forward()).
    • Unicast Miss / Broadcast / Multicast: Floods the frame out to all connected ports (br_flood()).

4. Routing, NAT, and External Connectivity
#

To forward container traffic to an external network (or route host traffic to a container), Linux uses Routing Tables and Netfilter NAT.

Route Tables
#

Linux uses multiple route tables evaluated by policy routing rules (ip rule):

  • local: Reserved for local IP addresses and loopback traffic.

  • main: Default table for standard route entries.

Enable kernel IP forwarding:
#

sysctl -w net.ipv4.ip_forward=1

Netfilter Architecture & Hooks
#

Table PrioritiesEvaluated in order: raw –> mangle –> nat –> filter.

flowchart TD
    Prerouting["PREROUTING
(DNAT / Port Forwarding)"] Route1{"Routing Decision"} Input["INPUT
(Local Filter)"] Process["Local Process"] Output["OUTPUT
(Outbound Filter)"] Forward["FORWARD
(Packet Routing)"] Route2{"Routing Decision"} Postrouting["POSTROUTING
(SNAT / Masquerade)"] %% Flow Path Prerouting --> Route1 %% Local Inbound & Outbound Route1 -- Local IP --> Input --> Process --> Output --> Route2 --> Postrouting %% Forwarding Path Route1 -- Other IP --> Forward --> Postrouting

Port Forwarding (Docker -p 80:80 equivalent)
#

  • Source NAT (SNAT / MASQUERADE): Rewrites private container IPs to host IPs for outbound internet access.
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j MASQUERADE
  • Destination NAT (DNAT): Redirects inbound traffic on host port 80 to container port 80.
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.2:80