Originally published at 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:
Prerequisite: ; it is just nicolaka/netshoot plus wireguard-tools.
WireGuard's data path runs in the host kernel, so the containers only need the wg userspace tool. The run script builds the image before deploying.
Running the Lab
The quick path, which builds the image, deploys, generates keys and configures peers, pings across the tunnel, captures the underlay and wg0 simultaneously, compares them, and tears everything down:
./scripts/labctl.sh run wg-16
Or step through it manually:
1. Move into the working directory
cd protocol-lab/examples/wg-16
2. Deploy
docker build -t protocol-lab/wireguard:latest .
sudo containerlab deploy -t wg-16.clab.yml
3. Generate keys and bring up the tunnel
Each node creates its own private key, and the two exchange public keys.
# key pairs
A_PRIV=$(docker exec clab-wg-16-node-a wg genkey)
A_PUB=$(printf '%s' "$A_PRIV" | docker exec -i clab-wg-16-node-a wg pubkey)
B_PRIV=$(docker exec clab-wg-16-node-b wg genkey)
B_PUB=$(printf '%s' "$B_PRIV" | docker exec -i clab-wg-16-node-b wg pubkey)
# node-a: create wg0, add B as a peer
docker exec clab-wg-16-node-a ip link add wg0 type wireguard
printf '%s' "$A_PRIV" | docker exec -i clab-wg-16-node-a sh -c \
"wg set wg0 private-key /dev/stdin listen-port 51820 \
peer $B_PUB endpoint 10.0.0.2:51820 allowed-ips 10.99.0.0/24"
docker exec clab-wg-16-node-a sh -c "ip addr add 10.99.0.1/24 dev wg0; ip link set wg0 up"
# node-b: symmetric
docker exec clab-wg-16-node-b ip link add wg0 type wireguard
printf '%s' "$B_PRIV" | docker exec -i clab-wg-16-node-b sh -c \
"wg set wg0 private-key /dev/stdin listen-port 51820 \
peer $A_PUB endpoint 10.0.0.1:51820 allowed-ips 10.99.0.0/24"
docker exec clab-wg-16-node-b sh -c "ip addr add 10.99.0.2/24 dev wg0; ip link set wg0 up"
(The private keys are passed via /dev/stdin — they are never written to a file.)
4. Ping across the tunnel
docker exec clab-wg-16-node-a ping -c3 10.99.0.2
docker exec clab-wg-16-node-a wg show wg0
If the ping succeeds and wg show reports latest handshake and transfer, the tunnel is working.
5. Capture in two places at once and compare
# underlay (the real link): UDP 51820 and (if any shows up) ICMP
docker exec -d clab-wg-16-node-a tcpdump -i eth1 -n -w /tmp/under.pcap "udp port 51820 or icmp"
# inside the tunnel
docker exec -d clab-wg-16-node-a tcpdump -i wg0 -n -w /tmp/inner.pcap "icmp"
docker exec clab-wg-16-node-a ping -c3 10.99.0.2
docker exec clab-wg-16-node-a pkill -INT tcpdump
docker exec clab-wg-16-node-a tcpdump -n -r /tmp/under.pcap # -> UDP 51820 only
docker exec clab-wg-16-node-a tcpdump -n -r /tmp/inner.pcap # -> ICMP echo
Expected Output
ping 10.99.0.2succeeds;wg show wg0lists the peer withlatest handshakeandtransfer.- Underlay capture:
10.0.0.1.51820 > 10.0.0.2.51820: UDP(ciphertext). No ICMP appears.
wg0capture:10.99.0.1 > 10.99.0.2: ICMP echo request(cleartext).
Why It Works
A tunnel is a mechanism for carrying overlay packets inside underlay packets (encapsulation). WireGuard does that with encryption — a very small VPN.
Peer = public key. WireGuard has no "username/password." Each peer is identified by its public key, andallowed-ipspins the overlay addresses that peer is allowed to claim (cryptokey routing). This doubles as authentication.
Carried over UDP. The encrypted packet is sent via UDP to the peer'sendpoint(IP:51820). That's why the underlay shows nothing but UDP/51820 — the inner IP packet (here, ICMP) is inside the ciphertext.
wg0is the decryption doorway. On the sending side, "handed to wg0" means "encrypt and transmit"; on the receiving side, packets are decrypted and emitted fromwg0. So capturingwg0shows cleartext. That isn't "eavesdropping" — you can read the contents only because you are the tunnel endpoint.
How it differs from TLS/DoT/DoH. Those wrap a specific protocol (a TCP stream, DNS). WireGuard wraps the IP packet itself, so whatever runs on top — TCP, UDP, or ICMP — gets encrypted wholesale. It operates at a different layer.
The key insight: the encryption applies to the underlay, and the inside is readable only at the endpoints. "I saw cleartext on wg0" does not mean "it isn't encrypted."
Common Pitfalls
Misreading the cleartext onwg0as "not encrypted."wg0is the post-decryption doorway. On the path (the underlay), the traffic is encrypted.
Mixing up underlay and overlay addresses. Underlay =10.0.0.0/24(the real link), overlay =10.99.0.0/24(the tunnel). You ping the overlay.
Swapping public and private keys. You hand your peer the public key. The private key never leaves its own node.
Underestimatingallowed-ips. It defines "the overlay addresses this peer may use," serving as both authentication and routing. Too broad is dangerous.
The kernel module. WireGuard's data path is the host kernel. Environments without the module won't work (recent Linux has it by default).
Endpoint direction. Each peer is told the other's endpoint (IP:port). With NAT traversal, one side alone can suffice (this lab fixes both sides).
Cleanup
sudo containerlab destroy -t wg-16.clab.yml --cleanup
If you used labctl.sh run wg-16, the script runs destroy for you at the end.
Check Your Understanding
- What is the difference between the underlay and the overlay? In this lab, which addresses are which?
- Why does the underlay capture show only UDP/51820 and no ICMP?
- Capturing
wg0shows cleartext ICMP. Does that mean the traffic "isn't encrypted"? Why or why not? - How does WireGuard identify a peer? What does
allowed-ipsdecide? - Compared with TLS (Lab 09) and DoT/DoH (Lab 14), what does a WireGuard tunnel wrap that they don't?
- Where does WireGuard's data path run — inside the container or on the host?
References
- . 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 protocols apart in the container lab — packet captures in hand, one layer at a time.
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
SOCIAL SHARE CARD GENERATOR