Monday, October 3, 2016

Implementing a Ping client in Python - Part II

Introduction

In Implementing a Ping client in Python - Part I, we gave an introduction to Ping, and showed how it is implemented using the ICMP protocol. Specifically, it uses the ICMP Echo Request and Response mechanism. The client sends the server an ICMP Echo packet, and the server responds with an ICMP Response packet.

We also defined the python class structure of the ICMP packet.

In this chapter, we will implement the serialization and deserialization routines so that we can send the packet over wire, and read the response back into a python class.

First, a word about byte ordering....

Byte Ordering


Every CPU architecture has a byte ordering specification. Byte ordering, also called Endianness defines how data types that are more than 1 byte in size are ordered in memory. For eg, if you have a word with value 0xAB, how is it written in computer memory, given that memory only stores bytes? Do you store the high order byte first (i.e at the lower memory location) and then the lower byte, or the other way around? This is called Endianness.

There are two kinds of Endianness, Big Endian and Little Endian.

Big Endian

In this scheme, the higher order byte is stored at the lower memory location. So, a 16 bit value 0xAB will be stored as follows:

OffsetValue
0 0xA
10xB

Little Endian


In Little Endian, the lower order byte is stored at the lower memory location, and then the high order byte. So, a 16 bit value 0xAB will be stored as follows:

OffsetValue
00xB
10xA

When sending data over the network, it is always sent in Big Endian order, i.e higher byte first. So, for our program, we will need to implement routines to send word and integer on the network, and read them back as well.

Network Byte Order Conversion

We will take the icmp_pkt class defined in the Part-I of this series, and add the following methods to it.


These two functions, _write_word and parse_word are the complements of each other.

_write_word serializes a 16byte value in BigEndian order.

parse_word reads a word from a buffer.

Note the usage of python ord function. Since the input to parse_word is a string ( parameter data is a string type) the character at position i in the buffer will be the character value of the byte. However, we are interested in the raw byte value, not the mapped unicode value. So, we will need to use the ord() function to map it back to it's raw value.

There is a slight discrepancy between _write_word() and parse_word() functions. _write_word takes a list. The reason for that is that the caller is going to pass in the list. Whereas parse_word() has to deal with network data that is stored in a string buffer returned from the socket call.

Next, we will write code to serialize the python class into a network buffer, and also deserialize it.

ICMP Packet Serialization/Deserialization


The ICMP echo packet structure is very simple. It just consists of bytes and words. There is also a variable length byte array to hold user data that is sent by the client and echoed back by the server. Serializing this is very simple. Just write them in order defined in the packet.

Note the starting offset of 20 in the parse() method. This is needed because on unix, when the socket.recvfrom() returns, it will read from the start of the IP header, which will be wrong. ICMP header is encapsulated inside the IP header, and we will need to go past
the IP header (which is 20 bytes in size) to read the ICMP response.


Conclusion


In the current installment, we looked at byte Encodings, specifically Big Endian and Little Endian. Then we saw how to encode the ICMP packet into a byte array for sending over the wire. We also saw how to parse the byte array back into a python class instance.

In the next installment, we will start to fill in the code for the checksum and sockets.