Overview
Our Private Island ® platform provides the ability to inject packets into the network and spoof other hosts. We developed a no-stack ICMP ECHO (ping) transmit for testing on the platform, and the notes below summarize the work.
ICMP provides control and diagnostic messages for layer 3 (Internet Protocol). ICMP packets are identified in the IP header protocol field with the value 0x1. A ubiquitous application of ICMPv4 is ping, and this command line tool is found on Linux, Mac, and Windows.
Background
ICMP on Linux
- The Linux kernel source for ICMPv4 can be found at net/ipv4/icmp.c
- struct icmphdr is defined in "include/uapi/linux/icmp.h"
struct icmphdr { __u8 type; __u8 code; __sum16 checksum; union { struct { __be16 id; __be16 sequence; } echo; __be32 gateway; struct { __be16 __unused; __be16 mtu; } frag; __u8 reserved[4]; } un; };
ICMP Header for Echo Request (ping)
The figure below depicts the ICMP fields that are embedded inside an IP packet (protocol = 1). For ECHO requests, the ICMP type is defined as 8 in RFC792. The 16-bit one's complement checksum is computed over all ICMP fields including the variable data field.
Ping
Our goal is to develop a bare-metal ping transmit that we can use to test both our device and its attachment to our network. Before doing so, we took a look at how ping is implemented on the Linux devices we work with.
On both Ubuntu 18.04 and Yocto, ping and ping6 are provided by iputils. The iputils project is hosted on Sourceforge and the most recent snapshot can be downloaded at skbuff.net
Clone and build iputils on Ubuntu
On Linux, it's simple enough to clone the Sourceforge repo and run make. For Ubuntu 18.04, libcap-dev is required.
If you're building this locally, then you'll probably want to debug it, too. Therefore, before running make, modify CFLAGS_DEFAULT in the Makefile to use '-Og' instead of '-O3'. You also may want to set the CAP_NET_RAW capabilities bits as shown below:
$ git clone https://git.code.sf.net/p/iputils/code iputils $ cd iputils $ make ping $ ./ping 192.168.1.1 ping: icmp open socket: Permission denied $ sudo setcap cap_net_raw=ep ./ping $ ./ping 192.168.1.1 PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.187 ms
Packet analysis of ping
Provided below is a packet dump of an ICMP echo request and reply, which we'll seek to emulate with our bare metal implementation. Note that we are running tcpdump on the host performing the reply.
$ tcpdump -ennvvxS -i eth2 icmp
10:10:01.203602 4d:49:4e:44:20:43 > 48:41:53:45:52:53, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 62344, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.0.101 > 192.168.0.102: ICMP echo request, id 13049, seq 1, length 64 0x0000: 4500 0054 f388 4000 4001 c504 c0a8 0065 0x0010: c0a8 0066 0800 85b2 32f9 0001 0000 0000 0x0020: 58fa 12b9 0000 0000 0003 14ca 1011 1213 0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 0x0050: 3435 3637 10:10:01.203662 48:41:53:45:52:53 > 4d:49:4e:44:20:43, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 55123, offset 0, flags [none], proto ICMP (1), length 84) 192.168.0.102 > 192.168.0.101: ICMP echo reply, id 13049, seq 1, length 64 0x0000: 4500 0054 d753 0000 4001 213a c0a8 0066 0x0010: c0a8 0065 0000 8db2 32f9 0001 0000 0000 0x0020: 58fa 12b9 0000 0000 0003 14ca 1011 1213 0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 0x0050: 3435 3637
You can see above (blue) that the ICMP code = 8 for the ping request and code = 0 for the reply. For both the reply and request, sequence = 1 and identifier = 0x32f9.
The data field for the ICMP ping / echo packet is loosely defined. If we refer to ping.c:send_probe(), we'll see that the first X bytes (machine specific but 16 bytes in our example and shown in green) are from a timeval struct returned from gettimeofday(). following the timestamp is an incrementing series of bytes (0x10 thorugh 0x37 in our example).
We now know enough to implement our own ICMP ping request. For our purposes, we'll just stuff the ICMP data field with an incrementing sequence of bytes. The source, including the checksum routine, is shown below: bm_send_probe(). Our hardware and supporting software (not shown here) provide the lower layer (2 and 3) packet headers and FCS checksum
uint16_t* bm_send_probe(uint16_t* pdata) { uint8_t tbuf[100]; // temporary buffer to build packet uint8_t* pbuf=tbuf; // pointer to the buffer uint8_t* pchksum = pbuf + 2; uint32_t chksum = 0; uint16_t temp; int i; /* Write the ICMPv4 Header */ *pbuf++ = 0x08; // type=8 *pbuf++ = 0x00; // code=0 /* Checksum Place Holder (set to 0 for checksum calc) */ *pbuf++ = 0; *pbuf++ = 0; /* Arbitrary Identifier */ *pbuf++ = 0x77; *pbuf++ = 0x33; /* Sequence Number */ *pbuf++ = 0x00; *pbuf++ = 0x01; temp=0x10; /* Now pad it with 56 incrementing bytes (64-8) */ for (i=0; i< 56; i++) { *pbuf++=temp++; } /* calculate the checksum */ chksum=0; for (i=0; i<64; i+=2) { chksum += tbuf[i]<<8 | tbuf[i+1]; } /* fold the upper word onto the lower word */ chksum = (chksum & 0xffff) + (chksum >> 16); /* 1's complement */ chksum = ~chksum & 0xffff; // write it out as network byte order ( big endian ) *pchksum++ = chksum >> 8; *pchksum = chksum & 0xff; /* copy buffer to PI */ for (i=0;i<63;i++) { *pdata++ = tbuf[i]; } *pdata = 0x100|tbuf[63]; // end of packet flag return (pdata); }
Packet analysis of bare metal ping
The last thing we need to do is test. A dump of the bare metal ping packet & reply is shown below, now sent from Private Island and spoofing the original host used to previously transmit.
$ tcpdump -ennvvxS -i eth2 icmp
00:14:10.854873 4d:49:4e:44:20:43 > 48:41:53:45:52:53, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 21723, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.0.101 > 192.168.0.102: ICMP echo request, id 30515, seq 1, length 64 0x0000: 4500 0054 54db 4000 4001 63b2 c0a8 0065 0x0010: c0a8 0066 0800 c7f6 7733 0001 1011 1213 0x0020: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 0x0030: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 0x0040: 3435 3637 3839 3a3b 3c3d 3e3f 4041 4243 0x0050: 4445 4647 00:14:10.854930 48:41:53:45:52:53 > 4d:49:4e:44:20:43, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 22163, offset 0, flags [none], proto ICMP (1), length 84) 192.168.0.102 > 192.168.0.101: ICMP echo reply, id 30515, seq 1, length 64 0x0000: 4500 0054 5693 0000 4001 a1fa c0a8 0066 0x0010: c0a8 0065 0000 cff6 7733 0001 1011 1213 0x0020: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 0x0030: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 0x0040: 3435 3637 3839 3a3b 3c3d 3e3f 4041 4243 0x0050: 4445 4647
ICMP and related RFCs:
- Internet Control Message Protocol: RFC792
- Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification: RFC4443
- Extended ICMP to Support Multi-Part Messages: RFC4884
- Internet Standard Subnetting Procedure: RFC950
- Extended ICMP to Support Multi-Part Messages: RFC4884
- Deprecation of ICMP Source Quench Messages: RFC6633
- Formally Deprecating Some ICMPv4 Message Types: RFC6918
- Requirements for IP Version 4 Routers: RFC1812 (Section 4.3 covers ICMP query messages)
- Requirements for Internet Hosts -- Communication Layers: RFC1122