The next stage was to get a working implementation of the IP layer. The best way to test this is to implement ping. IP headers have the following structure:

typedef struct
{
  EtherNetII eth;
  unsigned char hdrlen : 4;
  unsigned char version : 4;
  unsigned char diffsf;
  unsigned int len;
  unsigned int ident;
  unsigned int fragmentOffset1: 5;
  unsigned int flags : 3;
  unsigned int fragmentOffset2 : 8;
  unsigned char ttl;
  unsigned char protocol;
  unsigned int chksum;
  unsigned char source[4];
  unsigned char dest[4];
}IPhdr;

Eth is the ethernet header discussed in the previous part where destination is the router and the source is the enc28j60. The type is IP, 0x0800. Hdrlen is the length of the header also known as the offset to the actual data in this case it is 5 as it is in number of 32bit words. This is version 4 of the ip protocol. Diffsf will not be used and is set to zero. Len is the length of the data for now it will be ignored. Ident is a number identifying this packet it should be incremented after each packet but can be ignored. We wont be fragmenting the packet therefore fragment offset will be set to zero. The flags will have the middle bit set to ensure no fragmentation. TTL stands for time to live and is the number of seconds this packet should live for. For most situations this is set to 128. Protocol will be set latter for ICMP pings it is 0x1. Checksum will be calculated later. Source is the IP address we verified by ARP namely 192.168.0.50 and the destination is the router in this case 192.168.0.1.

Now that we have the IP header we can create a ping message using the following header.

typedef struct
{
  IPhdr ip;
  unsigned char type;
  unsigned char code;
  unsigned int chksum;
  unsigned int iden;
  unsigned int seqNum;
}ICMPhdr;

The ICMP header has only a few fields. Type will be the either reply 0x0 or request 0x8.  Code is unused and set to 0. Checksum is calculated later and set to zero. Identifier should be any number or whatever the request that your replying to is. Sequence number has same constraints as iden. After this header we have some data we are sending. For microsoft computers when they ping the data is abcdefghijklmnopqrstuvwabcdefghi. We can now add in the length of our packet into the ip layer remember to add in the length of the data and remove the length of the ethernet header.

The only thing now left is the checksum. The checksum is:

The checksum field is the 16-bit one’s complement of the one’s complement sum of all 16-bit words in the header. For purposes of computing the checksum, the value of the checksum field is zero.

I decided not to role my own for this and just used uips one.

static unsigned int
chksum(unsigned int sum, const unsigned char *data, unsigned int len)
{
  unsigned int t;
  const unsigned char *dataptr;
  const unsigned char *last_byte;

  dataptr = data;
  last_byte = data + len - 1;

  while(dataptr < last_byte) { /* At least two more bytes */
    t = (dataptr[0] << 8) + dataptr[1];
    sum += t;
    if(sum < t) {
      sum++; /* carry */
    }
    dataptr += 2;
  }

  if(dataptr == last_byte) {
    t = (dataptr[0] << 8) + 0;
    sum += t;
    if(sum < t) {
      sum++; /* carry */
    }
  }

  /* Return sum in host byte order. */
  return sum;
}

The checksums were then added into the packet and it was sent out.

The pings actually helped work out a few minor issues I was having with the ENC28J60 driver namely an extra byte of data always seemed to be sent and 4 extra bytes seemed to be recieved. It turned out there was a small bug with the driver for sending and for receiving. When sending the packet pointer for the enc28j60 was one byte off probably due to the control byte although more investigation will be required. When receiving the packets CRC is appended to the end of the packet since I am trying to make this as small as possible these last 4 bytes were dropped.

The next stage is to get a working TCP implementation the easiest way to test this is to try handshake with a server.

Advertisements