ICMP Echo (ping) without a Stack

Bare metal / no-stack implementation of ICMP ECHO ( ping ) based on insight from the implementation of ping on Linux.

advertisement

Overview

Our Private Island platform provides the ability to inject packets into our network and also spoof other hosts. We recently 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. 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;
    };
    
  • Incoming ICMP packets are received inside the kernel at icmp_rcv(struct sk_buff *skb), which can be found in net/ipv4/icmp.c.
  • The implementation of ICMP relies on the use of sockets, and user space applications (i.e., ping) make use of sockets to perform their function.

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.

0
8
16
24
Type=8
Code=0
Checksum
Identifier
Sequence Number
Data

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 16.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 16.04, libgcrypt20-dev and libcap-dev were 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(). The PI hardware and supporting software (not shown here) provide the lower layer (2 and 3) packet headers and FCS checksum

send_probe.c
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

Help us improve this article by adding your comment or question:

email addresses are neither displayed nor shared