DEV Community

VXLAN Explained: An Overlay You Can Read on the Wire

VXLAN Explained: An Overlay You Can Read on the Wire

Originally published at https://blog.pathvector.dev/protocol-lab-vxlan-18/ - part of the free Protocol Lab series.

This post is part of Protocol Lab, a free, hands-on series for learning networking protocols by building and breaking them in a container lab. All the lab material - topologies, configs, and scripts - lives in the repo: github.com/pathvector-studio/protocol-lab.

In Lab #16, WireGuard built an encrypted tunnel: the underlay showed only ciphertext. This lab builds a VXLAN overlay, which encapsulates the same way - inner packets inside outer UDP - but does not encrypt. So the underlay reveals both the VXLAN header and the inner frame in the clear. VXLAN also differs in what it carries: it is a Layer-2 overlay (it tunnels Ethernet frames), the technology data centers use to stretch one virtual L2 network across many physical hosts.

  • Reading guide: rfc-notes/vxlan-overlay.md
  • Prerequisites: Lab 16: WireGuard, TCP Lab 07 (reading captures)
  • Expected time: 45-60 minutes.

The Goal

You will:

  • Build a point-to-point VXLAN overlay (VNI 100) between two nodes
  • Ping across the overlay (10.200.0.0/24)
  • Capture the underlay and see VXLAN, vni 100 over UDP 4789 - with the inner ICMP echo visible

By the end, you should be able to compare Lab 16 and Lab 18:

WireGuard (Lab 16) VXLAN (Lab 18)
Carries any IP packet (L3) Ethernet frames (L2)
Encrypted? yes no
Underlay shows ciphertext only (UDP 51820) VXLAN header + inner frame (UDP 4789)

What You Will Learn

  • What VXLAN is: an L2-over-L3 overlay, identified by a 24-bit VNI, carried in UDP 4789
  • The difference between an overlay address (10.200.0.x) and the underlay address (10.0.0.x)
  • That encapsulation and encryption are separate: VXLAN encapsulates but does not encrypt
  • Why you can read the inner packet on the underlay here, but not in Lab 16
  • Where VXLAN is used (data-center network virtualization, EVPN)

This lab does not cover:

  • Multicast VXLAN, VTEP learning, or EVPN control planes (we use a static point-to-point remote)
  • VXLAN security (it usually rides on a trusted or separately encrypted underlay)
  • Bridging real Ethernet segments; we just put IP on the VXLAN interface

Where to Read in the RFCs

Reference What to focus on
RFC 7348 ยง4 The VXLAN frame format (outer UDP + VXLAN header + inner Ethernet)
RFC 7348 ยง5 The role of the VTEP (VXLAN Tunnel Endpoint)
RFC 7348 ยง3 Segmentation by the 24-bit VNI, and UDP port 4789
RFC 5737 ยง3 Confirming the addresses used here are documentation-only

The Big Picture

Two nodes. On top of the underlay (eth1, 10.0.0.0/24), we stretch a VXLAN overlay (vxlan0, 10.200.0.0/24).

overlay: 10.200.0.1 <== vxlan0 (VNI 100) ==> 10.200.0.2
              |                                    |
          node-a --------------- eth1 (underlay 10.0.0.0/24) --------------- node-b
          10.0.0.1                                                         10.0.0.2

When you ping the overlay address 10.200.0.2, VXLAN wraps the inner frame in UDP 4789 and sends it to the peer across the underlay. Because VXLAN does not encrypt, peeking at the underlay shows both the VXLAN header and the inner ICMP.

flowchart LR
    subgraph node-a
        p1["ping 10.200.0.2 (inner Ethernet/IP/ICMP)"] --> v1["vxlan0 encapsulate"]
    end
    subgraph node-b
        v2["vxlan0 decapsulate"] --> p2["ICMP delivered"]
    end
    v1 -- "UDP 4789 ยท VNI 100 (inner frame in the clear)" --> v2
    note["on the underlay, both the VXLAN header and the inner frame are readable"]

Note: Everything here uses local/documentation address space (RFC 5737), so nothing in this lab touches the real internet.

What You Need

Recommended environment: Linux / WSL2 / a Linux VM - the host needs the vxlan kernel module. It's standard on recent Linux and is auto-loaded the first time a vxlan interface is created.

Docker containerlab Images used: nicolaka/netshoot:latest - bundles ip and tcpdump (with VXLAN decoding). No additional images are required. The VXLAN data path runs in the host kernel.

Running the Lab

The quick path, which deploys, verifies, and tears down for you:

./scripts/labctl.sh run vxlan-18

Or step through it manually:

  1. Move into the working directory
cd protocol-lab/examples/vxlan-18
  1. Deploy
sudo containerlab deploy -t vxlan-18.clab.yml
  1. Bring up the VXLAN overlay
# node-a: peer (10.0.0.2) as remote, VNI 100, UDP 4789
docker exec clab-vxlan-18-node-a sh -c \
    "ip link add vxlan0 type vxlan id 100 remote 10.0.0.2 dstport 4789 dev eth1; \
     ip addr add 10.200.0.1/24 dev vxlan0; ip link set vxlan0 up"

# node-b: the mirror image
docker exec clab-vxlan-18-node-b sh -c \
    "ip link add vxlan0 type vxlan id 100 remote 10.0.0.1 dstport 4789 dev eth1; \
     ip addr add 10.200.0.2/24 dev vxlan0; ip link set vxlan0 up"
  1. Ping across the overlay
docker exec clab-vxlan-18-node-a ping -c3 10.200.0.2
docker exec clab-vxlan-18-node-a ip -d link show vxlan0
  1. Capture the underlay and confirm you can read the payload
docker exec -d clab-vxlan-18-node-a tcpdump -i eth1 -n -w /tmp/vx.pcap "udp port 4789"
docker exec clab-vxlan-18-node-a ping -c3 10.200.0.2
docker exec clab-vxlan-18-node-a pkill -INT tcpdump
docker exec clab-vxlan-18-node-a tcpdump -n -e -vv -r /tmp/vx.pcap

Right after the VXLAN, ... vni 100 line, the inner 10.200.0.1 > 10.200.0.2: ICMP echo request is visible as-is.

Expected Output

  • ping 10.200.0.2 succeeds
  • ip -d link show vxlan0 shows vxlan id 100 ... dstport 4789
  • The underlay capture shows VXLAN, ... vni 100 (UDP 4789), and inside it, the ICMP echo in plain text

Why It Works

VXLAN (Virtual eXtensible LAN) is an overlay that wraps L2 Ethernet frames in L3 UDP packets. It exists to let one virtual LAN be shared across physically separate hosts.

Encapsulation. The inner Ethernet frame (containing IP, containing ICMP) is wrapped in UDP 4789 + a VXLAN header. The VXLAN header carries the VNI, a 24-bit identifier that says which overlay the frame belongs to - up to about 16 million distinct overlays.

VTEP. The endpoint that encapsulates and decapsulates is called a VTEP. In this lab, each node's vxlan0 is the VTEP; specifying the peer VTEP's underlay address with remote gives us a point-to-point setup.

No encryption. VXLAN's job is only "wrap and carry." It does not encrypt. That's why capturing the underlay shows both the VXLAN header and the inner frame in plain text - and tcpdump decodes VXLAN and shows you the ICMP inside.

Contrast with Lab 16. WireGuard also "wraps in UDP and carries," but it encrypts the contents, so the underlay showed nothing but ciphertext. Encapsulation (wrapping) and encryption are separate functions. VXLAN does only the former; WireGuard does both.

To run VXLAN safely, use it over a trusted underlay or encrypt the underlay separately, e.g. with IPsec.

The key insight: wrapping and encrypting are independent. An overlay is not automatically confidential.

Common Pitfalls

  • Assuming the overlay is "encrypted." VXLAN does not encrypt. The contents are readable on the underlay.
  • Mixing up overlay and underlay addresses. Underlay = 10.0.0.0/24 (eth1); overlay = 10.200.0.0/24 (vxlan0). You ping the overlay.
  • Forgetting the VNI. Both ends must use the same VNI (here, 100) or nothing gets through.
  • Forgetting it's an L2 overlay. VXLAN carries Ethernet frames. This lab puts IP directly on vxlan0, but the real-world use is attaching it to a bridge to stretch an L2 segment.
  • Kernel module. The VXLAN data path is in the host kernel. Environments without it won't work (recent Linux ships it by default).
  • MTU. The VXLAN header shrinks the overlay's effective MTU (by roughly 50 bytes). Watch out for fragmentation with large packets.

Cleanup

sudo containerlab destroy -t vxlan-18.clab.yml --cleanup

If you used labctl.sh run vxlan-18, the script runs destroy for you at the end.

Check Your Understanding

  • What does VXLAN wrap in what (inner and outer)? Which port does it use?
  • What is a VNI? How many bits, and what does it distinguish?
  • In this lab, which addresses are the overlay and which are the underlay?
  • Why is the inner ICMP visible when you capture the underlay?
  • How is this different from Lab 16 (WireGuard)?
  • How do encapsulation and encryption differ? Which does VXLAN do, and which does WireGuard do?
  • If you want to use VXLAN securely, what should you combine it with?

References

  • RFC 7348: Virtual eXtensible Local Area Network (VXLAN)
  • RFC 5737: IPv4 Address Blocks Reserved for Documentation
  • ip-link(8) manual page (vxlan)

Verified Run Log (2026-07-07)

This lab has been confirmed reproducible on real hardware.

  • Environment: Ubuntu 26.04 LTS (kernel 7.0.0-27-generic, x86_64)
  • The vxlan kernel module auto-loaded when vxlan0 was first created
  • Docker 29.1.3
  • containerlab 0.77.0
  • node-a / node-b: nicolaka/netshoot:latest (tcpdump 4.99.6, with VXLAN decoding)

Running PATH="/tmp/pl-shim:$PATH" ./scripts/labctl.sh run vxlan-18 performed deploy โ†’ verify โ†’ destroy, and verification.json returned "status": "verified".

Ping across the overlay, and the VXLAN interface

$ docker exec clab-vxlan-18-node-a ping -c3 10.200.0.2
3 packets transmitted, 3 received, 0% packet loss

$ docker exec clab-vxlan-18-node-a ip -d link show vxlan0
... vxlan id 100 remote 10.0.0.2 dev eth1 srcport 0 0 dstport 4789 ...

The underlay shows both the VXLAN header and the inner frame

$ docker exec clab-vxlan-18-node-a tcpdump -n -e -vv -r underlay.pcap
10.0.0.1.46777 > 10.0.0.2.4789: [udp sum ok] VXLAN, flags [I] (0x08), vni 100
    10.200.0.1 > 10.200.0.2: ICMP echo request, id ..., seq 1, length 64
10.0.0.2.33408 > 10.0.0.1.4789: [udp sum ok] VXLAN, flags [I] (0x08), vni 100
    10.200.0.2 > 10.200.0.1: ICMP echo reply, id ..., seq 1, length 64

The outer layer is VXLAN ... vni 100 (UDP 4789), and inside it the ICMP echo request/reply is visible in plain text, as-is. VXLAN encapsulates but does not encrypt - the decisive difference from Lab 16's WireGuard, where the underlay showed ciphertext only. Wrapping and encrypting really are separate functions.

Cleanup

containerlab destroy -t vxlan-18.clab.yml --cleanup

That's VXLAN: an L2 overlay that wraps Ethernet frames in UDP and carries them anywhere the underlay reaches - with everything still readable on the wire, because encapsulation is not encryption.

Explore the full Protocol Lab series here: github.com/pathvector-studio/protocol-lab. If these labs are useful to you, please โญ star the repo on GitHub - it genuinely helps others find the project.

Next up, we'll keep pulling tunnels apart - looking at how overlays like this get a real control plane with EVPN, so VTEPs can find each other without static configuration.

Comments

No comments yet. Start the discussion.