# Implementing an ICMP Tunnel in the Linux Kernel: Encapsulating TCP/UDP in Echo Requests
An ICMP tunnel at the Linux kernel level allows encapsulating TCP/UDP traffic inside ICMP Echo Request packets, bypassing standard network filters. This approach is useful for researching the network stack, testing packet interception, and understanding how Netfilter works. The article breaks down the architecture of the kernel module that implements traffic translation through the NF_INET_POST_ROUTING and NF_INET_LOCAL_IN hooks using tasklets for asynchronous sending.
ICMP Tunnel Architecture in the Linux Kernel
The module consists of three key components: two Netfilter hooks and a tasklet for sending packets. The NF_INET_POST_ROUTING hook intercepts outgoing TCP/UDP packets before they are sent to the network, converts them into ICMP echo requests, and passes control to the tasklet. The second hook—NF_INET_LOCAL_IN—catches incoming ICMP packets, extracts the original traffic, and routes it to the loopback interface for delivery to applications. The tasklet handles deferred packet sending via dev_queue_xmit, minimizing blocking in the interrupt context.
Hook registration uses the NF_IP_PRI_FIRST priority to ensure first-order processing. This is critical, as interference from other modules beforehand could disrupt encapsulation or decapsulation integrity. Memory management relies on sk_buff—the core structure of the Linux network stack—which requires careful header reservation and proper release via kfree_skb in case of errors.
Detailed Breakdown of output_hook and create_packet_output
The output_hook function is called for every outgoing packet. It delegates ICMP wrapper creation to the create_packet_output function, which:
- Checks if the protocol is TCP or UDP—if not, returns NULL, and the packet continues its route unchanged.
- Retrieves the recipient's MAC address via
__ipv4_neigh_lookup, which is needed to form the Ethernet header. - Linearizes the
sk_buffwithskb_linearize, as subsequent data copying operations require a contiguous buffer. - Determines the transport protocol type (0 for UDP, 1 for TCP), header length, and payload size.
- Allocates a new
sk_buffaccounting for reserved space under headers (LL_RESERVED_SPACE) and tail data (needed_tailroom). - Sequentially builds the headers: Ethernet, IPv4, ICMP, copying the transport header and data into the ICMP body.
- Calculates checksums for IP and ICMP using
ip_fast_csumandip_compute_csum. - Returns the ready packet for sending.
A key detail is using the icmp->un.echo.id field to pass service information about the original protocol type and stream identifier. This allows the receiving side to correctly reconstruct the original packet without additional metadata outside the ICMP structure.
Handling Incoming Packets and Loopback Routing
On the receiving side, the input_hook analyzes every incoming ICMP packet. If it matches the tunnel format (Echo Request with a known pattern in the ID field), the module:
- Extracts the transport header and data from the ICMP body.
- Creates a new
sk_buffmimicking an incoming TCP/UDP packet. - Sets the destination device to the loopback interface (
dev_set_name(skb, "lo")). - Changes the MAC addresses to localhost and calls
netif_rx_ni()to inject the packet into the network stack.
This makes the kernel process the packet as if it came from outside, but via the lo interface. Applications receive the data unchanged, unaware of the encapsulation. Importantly, NF_STOLEN is not used here—the packet isn't "stolen" but replaced, so NF_ACCEPT is returned after successful injection.
Critical Technical Details and Limitations
The implementation faces several technical limitations:
- Fragmentation: ICMP packets may fragment en route, but the current implementation doesn't handle fragments—assuming an MTU sufficient for full encapsulation.
- Security: Lack of encryption or authentication makes the tunnel vulnerable to spoofing and MITM attacks.
- Performance: Each packet requires allocating a new
sk_buff, copying data, and computing checksums—this creates CPU overhead. - IPv4-only: The module doesn't support IPv6, as it uses
ip_hdrand__ipv4_neigh_lookup. - MAC dependency: Requires an ARP entry for the target IP; otherwise, the packet is dropped.
Despite these limitations, the project is excellent for learning: it covers Netfilter operations, kernel memory management, network header construction, and real-time OSI layer interactions.
Key Takeaways
- The ICMP tunnel encapsulates TCP/UDP in Echo requests, allowing it to bypass some network filters.
- Netfilter hooks with maximum priority are used to intercept traffic before/after routing.
- Tasklets enable asynchronous sending without blocking the interrupt context.
- On the receiving side, packets are injected into loopback for seamless delivery to applications.
- The implementation is educational—not intended for production due to missing security and fragment handling.
— Editorial Team
No comments yet.