38
I. Implementing ICMP in Rust
This post is inspired by Jon Gjengset series titled "Implementing TCP in Rust".
This should be an interesting read for anyone who wants to get a better understanding of networking protocols down to bits and doesn't mind diving into RFCs.
So what is ICMP anyway? It's the protocol that makes ping
and traceroute
possible. If you're a gamer or ever joined an online call, then you know how important those ping/latency numbers are.
Unlike TCP/UDP, which are Layer 4 protocols in OSI Model, ICMP actually belongs to Layer 3 which is the same level as IP, itself the main protocol of the Internet.
First, I'll show you what an IPv4 (RFC-791) header looks like, remember all your connections on the internet start with one of these:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Example Internet Datagram Header
To capture a raw IP frame, ping localhost
in another terminal and listen with:
$ tcpdump -i lo -p icmp -X
15:23:33.885666 IP localhost > localhost: ICMP echo request, id 21780, seq 1, length 64
0x0000: 4500 0054 9210 4000 4001 aa96 7f00 0001 E..T..@.@.......
0x0010: 7f00 0001 0800 492b 5514 0001 2d08 f460 ......I+U...-..`
0x0020: 0000 0000 6c83 0d00 0000 0000 1011 1213 ....l...........
0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 .............!"#
0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 $%&'()*+,-./0123
0x0050: 3435 3637 4567
15:23:33.885695 IP localhost > localhost: ICMP echo reply, id 21780, seq 1, length 64
0x0000: 4500 0054 9211 0000 4001 ea95 7f00 0001 E..T....@.......
0x0010: 7f00 0001 0000 512b 5514 0001 2d08 f460 ......Q+U...-..`
0x0020: 0000 0000 6c83 0d00 0000 0000 1011 1213 ....l...........
0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 .............!"#
0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 $%&'()*+,-./0123
0x0050: 3435 3637 4567
Let's start with the first IP and decode the header — the first 20 bytes of 4500 0054 9210 4000 4001 aa96 7f00 0001 7f00 0001 ...
:
▼
4 Version = IPv4 version
5 IHL = 20 bytes
00 ToS
0054 Total Length = 84 bytes
9210 Identification
4 Flags
000 Fragment Offset
40 Time to Live = 64 TTL
01 Protocol = ICMP
aa96 Header Checksum
7f00 0001 Source IP Address = 127.0.0.1
7f00 0001 Destination IP Address = 127.0.0.1
From RFC-792 we can see the layout of Echo or Echo reply message:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data ...
+-+-+-+-+-
And by replacing data
bytes, the remaining bytes after the header, we get our ICMP header:
08 Type = Echo message
00 Code
492b Checksum
5514 Identifier = id 21780 (in raw capture)
0001 Sequence Number = seq 1
If you compare this ICMP packet with the one from second IP frame, two fields have changed:
-
Type
which has become00
or Echo reply message -
Checksum
, naturally because content has changed
Now that we know what an ICMP packet looks like, we are ready to implement it in Rust.
Unlike TCP/UDP, we have no concept of ports here and only use IPs to find our path. (Now you understand why ping has no port?).
Furthermore, astute reader may have noticed that the only thing that changes in the ICMP echo reply is the Type
which goes from 08
to 00
.
We've got all we need. Check out: II. Implementing ICMP in Rust
38