ICMP & Path MTU
ICMP & Path MTU
Small pings succeed while large transfers hang: classic MTU / PMTUD failure. This chapter treats ICMP as a control helper and shows you how to find black holes with deliberate packet sizes.
Learning goals
By the end of this chapter you can:
- Explain key ICMP types used in operations
- Test reachability with controlled sizes and DF bit
- Predict PMTUD behavior when ICMP “fragmentation needed” is filtered
- Clamp MTU on a lab link and measure the symptom
- Document a harden checklist for MTU in topologies
ICMP essentials (IPv4)
| Type | Role |
|---|---|
| Echo request/reply | ping |
| Destination unreachable | no route, port unreachable, frag needed |
| Time exceeded | TTL expired (traceroute) |
| Redirect | host guidance (often ignored/noisy) |
ping -c 3 10.0.0.1
ping -c 3 -s 1400 10.0.0.1
ping -M do -s 1472 10.0.0.1 # DF set; 1472 + 28 = 1500-M do (Linux) sets Don’t Fragment. Adjust payload so total IP packet hits MTU boundary.
IPv6 uses ICMPv6 extensively (ND, PMTUD); similar lab methods with ping -6.
Path MTU discovery (idea)
- Sender assumes path MTU (often interface MTU)
- Sends DF packets
- Hop with smaller MTU sends ICMP frag needed + next-hop MTU
- Sender caches smaller PMTU
If step 3 is filtered, large packets blackhole; small pings work.
Lab: MTU mismatch
h1 -- r1 ======== r2 -- h2
MTU 1400 on r1-r2 link
Topology
name: mtu-lab
topology:
nodes:
r1:
kind: linux
image: alpine:3.20
exec:
- apk add --no-cache iproute2 iputils iptables
- sysctl -w net.ipv4.ip_forward=1
- ip addr add 192.168.1.1/24 dev eth1
- ip addr add 10.0.0.1/24 dev eth2
- ip link set eth1 up
- ip link set eth2 up
- ip link set eth2 mtu 1400
- ip route add 192.168.2.0/24 via 10.0.0.2
r2:
kind: linux
image: alpine:3.20
exec:
- apk add --no-cache iproute2 iputils iptables
- sysctl -w net.ipv4.ip_forward=1
- ip addr add 192.168.2.1/24 dev eth1
- ip addr add 10.0.0.2/24 dev eth2
- ip link set eth1 up
- ip link set eth2 up
- ip link set eth2 mtu 1400
- ip route add 192.168.1.0/24 via 10.0.0.1
h1:
kind: linux
image: alpine:3.20
exec:
- apk add --no-cache iproute2 iputils
- ip addr add 192.168.1.10/24 dev eth1
- ip link set eth1 up
- ip route add default via 192.168.1.1
h2:
kind: linux
image: alpine:3.20
exec:
- apk add --no-cache iproute2 iputils
- ip addr add 192.168.2.10/24 dev eth1
- ip link set eth1 up
- ip route add default via 192.168.2.1
links:
- endpoints: ["h1:eth1", "r1:eth1"]
- endpoints: ["h2:eth1", "r2:eth1"]
- endpoints: ["r1:eth2", "r2:eth2"]Predict
ping -c 2 192.168.2.10small default works
- Large DF ping across path may fail or trigger PMTUD depending on sizes
- Capture may show ICMP unreachable frag needed if not filtered
Observe
docker exec clab-mtu-lab-h1 ping -c 2 192.168.2.10
docker exec clab-mtu-lab-h1 ping -M do -s 1472 -c 2 192.168.2.10
docker exec clab-mtu-lab-h1 ping -M do -s 1200 -c 2 192.168.2.10
docker exec clab-mtu-lab-r1 ip link show eth2Inject: drop ICMP unreachable on r1
docker exec clab-mtu-lab-r1 iptables -A FORWARD -p icmp --icmp-type fragmentation-needed -j DROP
# also output path variants may need INPUT/OUTPUT depending on generation point
docker exec clab-mtu-lab-r1 iptables -A OUTPUT -p icmp --icmp-type fragmentation-needed -j DROPRetest large DF pings / large TCP if iperf available.
Predict: hard blackhole for large DF packets.
Restore
docker exec clab-mtu-lab-r1 iptables -FHarden
- Match MTUs both ends of a link
- Do not filter all ICMP on transit routers
- For TCP, MSS clamping on edges sometimes used as workaround—prefer fixing MTU/ICMP
# example TCP MSS clamp (awareness; use carefully)
iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360Traceroute and ICMP filters
traceroute -n 192.168.2.10If time-exceeded filtered, traceroute incomplete while data works. Journal both ICMP classes separately: PMTUD vs traceroute.
Capture workflow
docker exec clab-mtu-lab-r1 tcpdump -ni eth2 -c 40 icmp or '(ip[6:2] & 0x1fff) != 0'
docker exec clab-mtu-lab-h1 ping -M do -s 1472 -c 3 192.168.2.10Look for:
- Echo requests size
- ICMP type 3 code 4 (frag needed)
- Whether sender reduces size after
iperf3 large transfers
# on h2
apk add iperf3
iperf3 -s
# on h1
iperf3 -c 192.168.2.10Compare before/after MTU clamp and ICMP drop. TCP may stall if PMTUD blackhole.
IPv6 notes
IPv6 routers do not fragment in the same way; PMTUD is even more critical. Minimum link MTU is 1280.
ping -6 -M do -s 1400 <dst>
ip -6 route get <dst>Operational checklist
| Check | Command / action |
|---|---|
| Interface MTU | ip link |
| End-to-end small ping | ping -c 3 |
| DF large ping | ping -M do -s ... |
| ICMP allowed | policy review |
| TCP test | iperf3 / curl large |
| Tunnel overhead | account for encapsulation |
Tunnels (later chapters) reduce effective MTU—plan overhead.
Predict worksheet
Path MTU 1500 except one hop 1400; ICMP filtered.
- Ping default size 56 payload — ?
- DF packet size 1500 — ?
- TCP bulk transfer — ?
Answers: (1) usually ok; (2) blackhole; (3) often hang or extreme slowness depending on stack workarounds.
verify.sh additions
#!/usr/bin/env bash
set -euo pipefail
docker exec clab-mtu-lab-h1 ping -c 2 -W 1 192.168.2.10
# expect success for moderate DF size under 1400 path
docker exec clab-mtu-lab-h1 ping -M do -s 1200 -c 2 -W 1 192.168.2.10
echo OKOptional: assert large 1472 DF fails when MTU 1400—can be a negative test in failure mode only.
Summary
- ICMP is part of healthy networks (PMTUD, traceroute), not pure “attack surface” to zero
- Size-controlled pings expose MTU issues small pings hide
- Filtering frag-needed creates blackholes
- Match link MTUs; account for encapsulation overhead
- Always pair ping with a bulk TCP test when performance is in question
Next part: interior routing—OSPF and BGP basics plus policy/filters on free FRR images.