Objective
“The network works for small packets but not for big ones” is the most misdiagnosed report in this course, because every cheap test passes. Ping works. DNS works. SSH connects. The routing table is correct and the interface counters show no errors. And a file transfer stalls at a few kilobytes and never recovers.
By the end of this lab you will have built that failure deliberately, twice — once where PMTUD works and tells you exactly which hop is the constraint, and once where a single firewall rule turns the same topology into a silent black hole. You will walk the four-outcome diagnostic ladder from Part LI-05 with real output at each rung, and you will fix it two different ways: correctly, by permitting the ICMP that PMTUD depends on, and unilaterally, by clamping MSS when the drop is somebody else’s.
Along the way you will reproduce the trap that catches operators who have read
about clamping but not about where its value comes from — a clamp that commits,
shows up in show interfaces, and advertises a number that changes nothing.
Architecture
Four VyOS routers in a line. The line matters: MTU problems are path problems, and a two-node topology cannot have a hop in the middle.
+----------+ +----------+ +----------+ +----------+
| client | | r1 | | r2 | | server |
| eth1 .5 |--------| eth1 .1 | | eth1 .2 | | eth1 .2 |
| | LAN | eth2 .1 |--------| eth2 .1 |--------| |
+----------+ +----------+ TRANSIT+----------+ FAR +----------+
192.168.10.0/24 198.51.100.0/30 203.0.113.0/30
^
the constrained hop:
r2 eth2, MTU reduced in Task 2
| Segment | Prefix | client | r1 | r2 | server |
|---|---|---|---|---|---|
| LAN | 192.168.10.0/24 | eth1 · .5 | eth1 · .1 | — | — |
| TRANSIT | 198.51.100.0/30 | — | eth2 · .1 | eth1 · .2 | — |
| FAR | 203.0.113.0/30 | — | — | eth2 · .1 | eth1 · .2 |
There is deliberately no NAT. Addresses are visible end to end, so an ICMP error names a hop you can go and look at, and a capture on any router shows the same tuple.
eth0 on each router is a management interface on whatever bridge your
hypervisor already uses. No task configures it and no route points at it —
which matters here more than usual, because Task 3 installs a firewall rule and
Task 6 reduces an MTU, and you want a way in that neither of those touches.
Requirements
- A hypervisor with roughly 4 GiB of free RAM and 32 GiB of free disk — four VMs at 1 GiB and 8 GiB each.
- The VyOS 1.5 LTS ISO. MSS clamping moved onto the interface in 1.4 and the firewall lost its per-interface bindings at the same time; nothing here commits on 1.3.
- Three isolated layer-2 segments — bridges with no physical port.
- Console access to all four routers.
- Roughly 100 minutes.
- Two consoles on
client: several tasks run a probe in one and a transfer in the other.
Scenario
A site has just moved its backups to an off-site target. The backup job runs for about four seconds, transfers roughly nothing, and then sits there until it is killed. Everything else on the circuit is fine. The monitoring system pings the off-site target every minute and has never reported a failure, which is being cited in the ticket as evidence that the network is not the problem.
Your job is to establish what the path can actually carry, decide whether the mechanism that is supposed to negotiate that is working, and — if it is not — work out whose it is to fix and what you can do without them.
Tasks
Configuration blocks are written for the [edit] prompt. Where a block opens
with configure and you are already at [edit], skip that line. Blocks tagged
bash are the operational-mode shell, as their comments say.
Task 1 — Build the line and establish the path MTU baseline
Install four routers, give each a hostname and a management address on eth0,
and capture the starting state.
# Run from operational mode on each of the four routers.
JOURNAL="$HOME/lab21"
mkdir -p "$JOURNAL"
show configuration commands > "$JOURNAL/pre-lab-config.txt"
ip -brief link show > "$JOURNAL/pre-lab-mtus.txt"
cat "$JOURNAL/pre-lab-mtus.txt"
That second file is the beginning of this lab’s MTU inventory, and it is worth having before anything is changed.
r1:
configure
set system host-name r1
set interfaces ethernet eth1 address 192.168.10.1/24
set interfaces ethernet eth1 description 'LAN'
set interfaces ethernet eth2 address 198.51.100.1/30
set interfaces ethernet eth2 description 'TRANSIT to r2'
set protocols static route 203.0.113.0/30 next-hop 198.51.100.2
commit
save
r2:
configure
set system host-name r2
set interfaces ethernet eth1 address 198.51.100.2/30
set interfaces ethernet eth1 description 'TRANSIT to r1'
set interfaces ethernet eth2 address 203.0.113.1/30
set interfaces ethernet eth2 description 'FAR - the hop this lab constrains'
set protocols static route 192.168.10.0/24 next-hop 198.51.100.1
commit
save
client and server:
configure
set system host-name client
set interfaces ethernet eth1 address 192.168.10.5/24
set protocols static route 0.0.0.0/0 next-hop 192.168.10.1
commit
save
configure
set system host-name server
set interfaces ethernet eth1 address 203.0.113.2/30
set protocols static route 192.168.10.0/24 next-hop 203.0.113.1
set service ssh port 22
commit
save
Now the baseline, from the client, because the client is the host that will eventually be suffering:
ping 203.0.113.2 count 3
ping 203.0.113.2 size 1472 do-not-fragment count 3
$ ping 203.0.113.2 size 1472 do-not-fragment count 3PING 203.0.113.2 (203.0.113.2) 1472(1500) bytes of data.
1480 bytes from 203.0.113.2: icmp_seq=1 ttl=62 time=1.31 ms
1480 bytes from 203.0.113.2: icmp_seq=2 ttl=62 time=1.02 ms
1480 bytes from 203.0.113.2: icmp_seq=3 ttl=62 time=0.98 ms
--- 203.0.113.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet lossIllustrative output
The arithmetic in the first line is the thing to internalise, because it is the
source of most confusion about this probe. size is the ICMP payload. Add
8 bytes of ICMP echo header and 20 bytes of IPv4 header and you have the IP
packet: 1472 + 28 = 1500. Which is why the operator’s rule is subtract 28 from
the MTU you are testing.
Write into the journal: path MTU is at least 1500 today. Nothing in this lab is comparable to anything until that line exists.
Task 2 — Introduce the smaller hop, and let PMTUD find it
On r2, reduce the MTU on the far segment. This is the ordinary case: a hop somewhere along the path that is not 1500, because it is a tunnel, a provider handoff, a PPPoE circuit or a partner network.
configure
set interfaces ethernet eth2 mtu 1400
compare
commit-confirm 5
commit-confirm because an MTU change on a live interface briefly takes the
link down while the NIC re-negotiates, and on a production router that is the
interface you may be reaching it through. Confirm once you still have a
session:
run show interfaces ethernet eth2
confirm
save
Now re-run the same probe from the client:
$ ping 203.0.113.2 size 1472 do-not-fragment count 3PING 203.0.113.2 (203.0.113.2) 1472(1500) bytes of data.
From 198.51.100.2 icmp_seq=1 Frag needed and DF set (mtu = 1400)
From 198.51.100.2 icmp_seq=2 Frag needed and DF set (mtu = 1400)Illustrative output
Read that line twice, because both halves are load-bearing and each answers a different question.
Frag needed and DF setis the ICMP a router sent because it could not forward your packet. Its presence means Path MTU Discovery is working. Do not reach for a clamp here; the mechanism is doing its job.mtu = 1400is the size that hop can forward. Your source now caches 1400 as the path MTU for this destination.From 198.51.100.2names the hop whose MTU is the constraint. That is the device to go and look at — and here it isr2, which is exactly where you put it.
An error attributed to your own address instead — ping: local error: message too long — means the outgoing interface on the box you are typing on is
the limit, and nothing upstream is involved at all. Different problem, different
router.
Now bisect, so that the number in your journal is measured rather than read off an ICMP you might be misinterpreting:
# On the client, operational mode. Work from small to large and read the boundary.
for size in 1300 1372 1400 1472; do
echo "=== payload $size ==="
ping -M do -s "$size" -c 2 -W 2 203.0.113.2
done
1372 is the payload that makes a 1400-byte packet — the same subtract-28 rule. Expect 1300 and 1372 to succeed and 1400 and 1472 to fail, which puts the path MTU at exactly 1400 and agrees with the ICMP.
Note in the journal that the monitoring system’s minute-by-minute ping has been succeeding throughout, because a default ping is 64 bytes and 64 bytes fit anywhere. The ticket’s claim that “the network is fine” was true of every packet the monitoring ever sent.
Task 3 — Break PMTUD with one rule, and find the drop on your own counter
PMTUD depends on ICMP getting back to the original sender. Take it away, using the rule that a security review produces about once a year.
On r1:
configure
set firewall ipv4 forward filter default-action accept
set firewall ipv4 forward filter rule 10 description 'over-zealous ICMP filter - the fault this task builds'
set firewall ipv4 forward filter rule 10 protocol icmp
set firewall ipv4 forward filter rule 10 icmp type-name fragmentation-needed
set firewall ipv4 forward filter rule 10 action drop
set firewall ipv4 forward filter rule 10 log
compare
commit
Now work the ladder from Part LI-05, from the client, and record what each rung returns.
# On the client. Four rungs, in this order, and read each before running the next.
ping -c 3 203.0.113.2 # rung 1: does it work at all?
ping -M do -s 1372 -c 3 203.0.113.2 # rung 2: does it work below the bottleneck?
ping -M do -s 1472 -c 3 203.0.113.2 # rung 3: does the ICMP come back?
ping -M dont -s 1472 -c 3 203.0.113.2 # rung 4: can the path carry fragments?
The pattern that identifies a black hole is precise: rungs 1, 2 and 4 succeed and rung 3 hangs with no ICMP at all. That is outcome 3 of the four in Part LI-05 — the path can deliver fragments but cannot deliver a non-fragmented large packet, because the message that would have told the sender to make it smaller never arrives.
Compare that with Task 2, where rung 3 returned Frag needed and DF set. Same
topology, same MTU, one firewall rule between them, and completely different
evidence.
Now find the drop. This is your firewall, so the counter is yours to read:
$ show firewall ipv4 forward filteripv4 firewall forward filter
Rule Action Protocol Packets Bytes Conditions
----- ------ -------- ------- ----- ----------
10 drop icmp 6 336 icmp type 3 code 4
default accept all 482 51204Illustrative output
Six packets, matching the six large probes you sent across two runs. Read the Packets column, not the rule’s existence — a correct-looking ICMP rule with a zero counter is not evidence that ICMP is arriving, it is evidence that nothing is reaching that rule.
Fix it, which for a filter you own is the right fix and not a workaround:
set firewall ipv4 forward filter rule 10 action accept
set firewall ipv4 forward filter rule 10 description 'ICMP fragmentation-needed - required for PMTUD'
compare
commit
save
Re-run rung 3 from the client and confirm Frag needed and DF set (mtu = 1400)
is back. Write into the journal: when the filter is yours, permit the message.
Clamping over a working PMTUD is a change that hides a fault instead of fixing
it, and it will still be there when the path MTU changes.
Task 4 — Prove it breaks TCP, not just ping
Restore the black hole, because the next three tasks need it — and this time watch what it does to something a user cares about.
configure
set firewall ipv4 forward filter rule 10 action drop
commit
On server, start the listener:
monitor bandwidth-test accept
On client, console one — a session that uses small packets:
ssh vyos@203.0.113.2
That connects and works. Type in it; it behaves normally. Now console two — a transfer that uses full-size segments:
monitor bandwidth-test initiate 203.0.113.2
That stalls. The client’s TCP negotiated an MSS of 1460 from its own 1500-byte
interface, builds 1500-byte packets, and every one of them is discarded at r2
without a word. TCP retransmits the same oversized segment, on a timer that
doubles, forever.
Capture the shape of it from r1:
# On r1, operational mode. Watch the same segment go out again and again.
sudo tcpdump -ni eth2 -c 10 'tcp port 5001'
Task 5 — Move the drop out of reach, and clamp
In production, the ICMP is usually being discarded somewhere you do not
administer. Simulate that by moving the drop onto r2 — the router that
generates the message — where you have no authority and, importantly, where
the traffic is locally originated and therefore in the output chain.
On r1, remove your rule entirely:
configure
delete firewall ipv4 forward filter
commit
save
On r2, playing the provider:
configure
set firewall ipv4 output filter default-action accept
set firewall ipv4 output filter rule 10 description 'provider does not emit ICMP unreachables'
set firewall ipv4 output filter rule 10 protocol icmp
set firewall ipv4 output filter rule 10 icmp type-name fragmentation-needed
set firewall ipv4 output filter rule 10 action drop
commit
save
Confirm from the client that rung 3 hangs again, and that r1’s firewall is now
empty — the drop is no longer yours, and no amount of work on r1’s rule set
will recover it.
Now apply the fix you can make unilaterally. The path MTU is 1400, so the IPv4 MSS is 1400 − 40 (20 IPv4 + 20 TCP) = 1360, and the IPv6 value on a dual-stack link would be 1400 − 60 = 1340.
configure
set interfaces ethernet eth2 ip adjust-mss 1360
set interfaces ethernet eth2 ipv6 adjust-mss 1340
compare
commit-confirm 5
Three properties of that node decide whether it does what you intended, and all three come from Part LI-04:
- It rewrites the MSS option in TCP SYN packets leaving that interface. It does not touch established connections, non-SYN packets, or anything that is not TCP.
ipandipv6are separate nodes with separate values. Setting one does nothing for the other, and forgetting the second produces a router where IPv4 transfers work and IPv6 transfers hang — a much harder ticket to read than “everything is broken”.- It applies to every TCP flow crossing that interface. There is no source, port or rule-set selector.
Prove it with a paired capture, on two interfaces, for the same connection. Open a fresh connection: the clamp acts on the handshake, so a session that is already up keeps whatever MSS it negotiated, and a rollback that “did not work” is usually a rollback tested on an old connection.
# On r1, console one - the LAN side, before the clamp is applied.
sudo tcpdump -ni eth1 -c 3 'tcp[tcpflags] & tcp-syn != 0'
# On r1, console two - the WAN side, after the clamp.
sudo tcpdump -ni eth2 -c 3 'tcp[tcpflags] & tcp-syn != 0'
$ sudo tcpdump -ni eth2 -c 3 'tcp[tcpflags] & tcp-syn != 0'IP 192.168.10.5.44112 > 203.0.113.2.5001: Flags [S], seq 2884113, win 64240, options [mss 1360,sackOK,TS val 12 ecr 0,nop,wscale 7], length 0Illustrative output
mss 1460 on eth1 and mss 1360 on eth2 is the proof. One reading is
suggestive; the pair is conclusive, and it is the pair that distinguishes “the
clamp is on the wrong interface” from “the clamp is not committed”.
Re-run the bandwidth test. It should now complete: the far end never sends a segment larger than the path can carry, so nothing needs the ICMP that is being discarded.
Confirm and save.
Task 6 — The clamp-mss-to-pmtu trap
adjust-mss also accepts a keyword instead of a number, and its name invites
exactly the wrong assumption. Reproduce the trap rather than reading about it.
configure
set interfaces ethernet eth2 ip adjust-mss clamp-mss-to-pmtu
compare
commit
Open a fresh connection and re-run the eth2 capture:
$ sudo tcpdump -ni eth2 -c 3 'tcp[tcpflags] & tcp-syn != 0'IP 192.168.10.5.44118 > 203.0.113.2.5001: Flags [S], seq 9911024, win 64240, options [mss 1460,sackOK,TS val 44 ecr 0,nop,wscale 7], length 0Illustrative output
mss 1460. The node is committed, show interfaces ethernet eth2 reports it,
and the bandwidth test stalls again exactly as it did before any clamp existed.
The reason is that clamp-mss-to-pmtu derives its value from this
interface’s own MTU, not from the path. eth2 is at the default 1500, so it
advertises 1500 − 40 = 1460, and the bottleneck two hops away at 1400 is
untouched. The keyword is useful precisely when the constrained interface is the
one carrying the clamp — a tunnel whose own MTU is the ceiling, for instance —
and useless when the constraint is somewhere else.
Put the number back:
set interfaces ethernet eth2 ip adjust-mss 1360
compare
commit
save
Write both captures into the journal, side by side. The trap is worth a page in your own notes, because everything about it looks correct: the configuration is committed, the syntax is valid, the feature is the right feature, and the number on the wire is wrong.
Task 7 — A tunnel, its overhead, and where the clamp belongs
Restore the topology to a clean 1500 everywhere, so the only constraint left is one you compute rather than one you configured.
On r2:
configure
delete interfaces ethernet eth2 mtu
delete firewall ipv4 output filter
commit
save
On r1:
configure
delete interfaces ethernet eth2 ip adjust-mss
delete interfaces ethernet eth2 ipv6 adjust-mss
commit
save
Confirm from the client that ping 203.0.113.2 size 1472 do-not-fragment count 3
succeeds again before going on.
Now build a GRE tunnel between r1 and r2 over the transit segment, and route
the client’s traffic through it.
On r1:
configure
set interfaces tunnel tun0 encapsulation gre
set interfaces tunnel tun0 source-address 198.51.100.1
set interfaces tunnel tun0 remote 198.51.100.2
set interfaces tunnel tun0 address 192.0.2.1/30
set interfaces tunnel tun0 description 'GRE to r2 - inner MTU is 1500 minus 24'
set protocols static route 203.0.113.0/30 next-hop 192.0.2.2
delete protocols static route 203.0.113.0/30 next-hop 198.51.100.2
commit
save
On r2:
configure
set interfaces tunnel tun0 encapsulation gre
set interfaces tunnel tun0 source-address 198.51.100.2
set interfaces tunnel tun0 remote 198.51.100.1
set interfaces tunnel tun0 address 192.0.2.2/30
set interfaces tunnel tun0 description 'GRE to r1'
set protocols static route 192.168.10.0/24 next-hop 192.0.2.1
delete protocols static route 192.168.10.0/24 next-hop 198.51.100.1
commit
save
Do the arithmetic before you read the interface. GRE over IPv4 adds a 20-byte outer IPv4 header and a 4-byte GRE header: 24 bytes. The transit segment is 1500, so the tunnel’s inner MTU is 1500 − 24 = 1476. Now read what the kernel chose:
run show interfaces tunnel tun0
Linux derives a GRE tunnel’s MTU from the underlying interface, so this is very likely to already be 1476 and your arithmetic is a check rather than a configuration step. Set it explicitly only if it disagrees with the number you computed — and if it does, find out why before overriding it:
configure
set interfaces tunnel tun0 mtu 1476
commit
Confirm the new path MTU empirically from the client, and notice that the constraint has moved without anyone configuring an MTU anywhere:
# On the client. 1448 payload is a 1476-byte packet; 1472 is 1500.
ping -M do -s 1448 -c 2 203.0.113.2
ping -M do -s 1472 -c 2 203.0.113.2
The second should return Frag needed and DF set (mtu = 1476) from
192.168.10.1 — r1 itself this time, because r1 is the router that cannot
fit the packet into tun0. That is a different hop reporting a different
constraint from the same probe, and it is why an MTU inventory records the
arithmetic and not just the number.
Finally, put the clamp where it belongs. Not on eth2, which is at 1500 and is
no longer the bottleneck — on the tunnel interface, whose MTU is the ceiling:
configure
set interfaces tunnel tun0 ip adjust-mss 1436
set interfaces tunnel tun0 ipv6 adjust-mss 1416
compare
commit
save
1476 − 40 = 1436 for IPv4, and 1476 − 60 = 1416 for IPv6. Note that this is the
one case where clamp-mss-to-pmtu would have produced the right answer, because
here the interface carrying the clamp is the interface whose MTU is the
constraint. Try it, capture the SYN on tun0, and confirm you get 1436 either
way — then decide which form you want in your own configurations, and write
down why.
Close by building the MTU inventory this lab has been accumulating: every interface on all four routers, its MTU, and for every value that is not 1500, the arithmetic that produced it. That table is the deliverable. A number without its arithmetic is a number nobody dares change.
Validation
- The Task 1 baseline exists:
ping 203.0.113.2 size 1472 do-not-fragmentsucceeded before anything was constrained. - The Task 2 bisection is in the journal and puts the boundary at a 1400-byte
packet, agreeing with the
mtu = 1400the ICMP reported. - The journal names
198.51.100.2as the hop that reported the constraint, and identifies it asr2. - The four-rung ladder was run under the black hole and the results match outcome 3: rungs 1, 2 and 4 succeed, rung 3 hangs.
show firewall ipv4 forward filteronr1showed rule 10’s packet counter incrementing by the number of large probes sent.- A paired SYN capture exists showing
mss 1460oneth1andmss 1360oneth2, taken on a freshly opened connection. - A capture exists showing
mss 1460oneth2whileclamp-mss-to-pmtuwas configured, alongside the note that the path MTU was 1400 at the time. - After Task 7,
show interfaces tunnel tun0reports MTU 1476, and a 1472-payload probe from the client returnsmtu = 1476from192.168.10.1. - The MTU inventory covers all four routers and carries arithmetic for every non-1500 value.
Expected Outcome
A four-router path whose MTU you have measured rather than assumed, and a working understanding of the three states it can be in: PMTUD working and telling you which hop to fix; PMTUD black-holed by a filter you own, which you fix by permitting the message; and PMTUD black-holed by somebody else, which you work around by clamping MSS on the interface that faces the constraint.
Plus one negative result that is worth as much as the rest: a clamp that committed, appeared in the configuration, and advertised a number that changed nothing.
Troubleshooting
ping ... size 1472 do-not-fragment fails from the start. Check the client’s
own interface MTU with show interfaces ethernet eth1. If the error is
local error: message too long, the constraint is on the box you are typing on
and nothing on the path is involved.
No ICMP comes back even with the firewall rule removed. Confirm the rule is
actually gone — show firewall ipv4 forward filter with no rules is different
from a rule set whose default action is drop. Then check r2’s output chain; a
leftover rule from Task 5 produces exactly this.
The bandwidth test works even before clamping. Check that r2’s eth2 MTU
really committed: show interfaces ethernet eth2 on r2. Also confirm the
traffic is taking the path you think — with the tunnel from Task 7 present, the
1400-byte hop may no longer be on the route.
tcpdump shows no SYN packets at all. The filter expression is matching
nothing because the connection is being reused. Kill the session and open a new
one; a clamp only ever acts on a handshake.
The clamp is committed and eth2 still shows mss 1460. Either the value
is clamp-mss-to-pmtu and you are looking at Task 6’s trap, or the clamp is on
an interface this traffic does not leave through. Read
show configuration commands | match 'adjust-mss' and compare against
show ip route 203.0.113.2.
The GRE tunnel is up but nothing crosses it. Confirm both ends have each
other as remote and that the static routes were switched to the tunnel
next-hop and the old ones deleted — two default paths to the same prefix, one
through the tunnel and one around it, is a coin toss.
A transfer that used to work fails after Task 7. The tunnel lowered the path MTU by 24 bytes and nothing has clamped yet. That is the lab working; apply the clamp from the end of Task 7.
Cleanup
Everything is on isolated bridges, so cleanup restores a known state rather than service.
Step 1. If you are keeping the topology for the MTU break/fix scenario, stop
here and save on each router. Note in the journal that traffic is running
through a GRE tunnel with a 1476-byte MTU and a 1436 clamp, so the next person
does not spend an hour discovering it.
Step 2. To return to the Task 1 state, on r1:
configure
delete interfaces tunnel tun0
set protocols static route 203.0.113.0/30 next-hop 198.51.100.2
delete protocols static route 203.0.113.0/30 next-hop 192.0.2.2
delete firewall ipv4 forward filter
compare
commit
save
and on r2:
configure
delete interfaces tunnel tun0
set protocols static route 192.168.10.0/24 next-hop 198.51.100.1
delete protocols static route 192.168.10.0/24 next-hop 192.0.2.1
delete interfaces ethernet eth2 mtu
delete firewall ipv4 output filter
compare
commit
save
Note the order inside each block: the replacement static route is set in the same commit that removes the tunnel route. Deleting the tunnel first and adding the route afterwards leaves the prefix unreachable in between, which on this lab is harmless and on a production edge is an outage you scheduled by accident.
Step 3. Confirm the restoration empirically rather than by reading the configuration back. From the client:
ping -M do -s 1472 -c 3 203.0.113.2
A clean 1500-byte path is the Task 1 baseline, and matching it is the proof that cleanup finished.
Step 4. To reset a router entirely, load the configuration captured in Task 1:
configure
load /config/pre-lab-21.boot
compare
commit
save
Step 5. To remove the topology, delete the four VMs and the three bridges on the hypervisor.
What You Learned
sizeis the ICMP payload, and the constant is 28. Subtract 28 from the MTU you want to test. Almost every confused MTU conversation contains somebody who is off by that number.- An ICMP Fragment Needed is good news. It means PMTUD is working, it names the constraint, and it names the hop that reported it. The correct response is to go and look at that hop, not to reach for a clamp.
- The four-outcome ladder separates four different problems. You ran it and got outcome 3 — fragments cross, non-fragmented large packets do not — and that single result told you the cause without any guessing.
- The chain matters. A Fragment Needed message crossing a router toward a host behind it is transit traffic and hits the forward chain; the same message generated by the router itself hits the output chain. “We already allow ICMP” is a claim about one of those and usually not the other.
- Small things working is the symptom, not the reassurance. Ping, DNS and SSH all worked throughout the black hole. The monitoring system never registered a fault, because it never sent a packet large enough to find one.
- Where the filter is yours, permit the message. Clamping over a working PMTUD hides the fault and outlives the path MTU it was written for.
- A clamp goes on the interface that faces the constraint, and it rewrites only what leaves that interface, only in SYN packets, only for TCP, and only for the address family whose node you set.
clamp-mss-to-pmtuuses the local interface MTU, not the path. You watched it advertise 1460 into a 1400-byte path, from a configuration that committed cleanly and looked exactly right.- Tunnel overhead is arithmetic you do before you read the interface. GRE over IPv4 costs 24 bytes; the MSS is the inner MTU minus 40 for IPv4 and minus 60 for IPv6; and the inventory records the arithmetic, because a number without it is a number nobody will ever dare change.