How to structure and ARP request packet in C

3.2k views Asked by At

Today I was investing a little more time to learn about ARP packets. To understand it's structure I tried to build one on myself in C using libpcap. I structured a simple ARP request packet and used pcap_inject function to send the packet. This function returns the number of bytes that are sent.

When I debug my code I saw that my packet was 42 bytes long. I search the Internet a bit and couldn't find a answer that tells me if this is the appropriate size for an ARP request or not. Even the wikipedia entry confused me a little. And the I discovered this post. From the answer provided by the user:

  • If the ARP message is to be sent in an untagged frame then the frame overhead itself is 18 bytes. That would result in a frame of 28+18=46 bytes without padding. Additional 18 bytes of padding are necessary in this case to bloat the frame to the 64 byte length.
  • If the ARP message is to be sent in an 802.1Q-tagged frame then the frame overhead is 22 bytes, resulting in the total frame size of 28+22=50 bytes. In this case, the padding needs to be 14 bytes long.
  • If the ARP message is to be sent in a double-tagged frame then the frame overhead is 26 bytes, resulting in the total frame size of 54 bytes. In this case, the padding needs to be 10 bytes long.

My question is what do I have to do in this situation. Do I have to use padding or not?

Bellow I post the structure of my packet.

#define ETH_P_ARP 0x0806 /* Address Resolution packet */
#define ARP_HTYPE_ETHER 1  /* Ethernet ARP type */
#define ARP_PTYPE_IPv4 0x0800 /* Internet Protocol packet */

/* Ethernet frame header */
typedef struct {
   uint8_t dest_addr[ETH_ALEN]; /* Destination hardware address */
   uint8_t src_addr[ETH_ALEN];  /* Source hardware address */
   uint16_t frame_type;   /* Ethernet frame type */
} ether_hdr;

/* Ethernet ARP packet from RFC 826 */
typedef struct {
   uint16_t htype;   /* Format of hardware address */
   uint16_t ptype;   /* Format of protocol address */
   uint8_t hlen;    /* Length of hardware address */
   uint8_t plen;    /* Length of protocol address */
   uint16_t op;    /* ARP opcode (command) */
   uint8_t sha[ETH_ALEN];  /* Sender hardware address */
   uint32_t spa;   /* Sender IP address */
   uint8_t tha[ETH_ALEN];  /* Target hardware address */
   uint32_t tpa;   /* Target IP address */
} arp_ether_ipv4;

In the end I just copy each structure member in the bellow order and send the packet:

void packageARP(unsigned char *buffer, ether_hdr *frameHeader, arp_ether_ipv4 *arp_packet, size_t *bufferSize) {
  unsigned char *cp;
  size_t packet_size;

  cp = buffer;

  packet_size = sizeof(frameHeader->dest_addr) 
                + sizeof(frameHeader->src_addr)  
                + sizeof(frameHeader->frame_type)
                + sizeof(arp_packet->htype)       
                + sizeof(arp_packet->ptype)    
                + sizeof(arp_packet->hlen)        
                + sizeof(arp_packet->plen)       
                + sizeof(arp_packet->op)        
                + sizeof(arp_packet->sha)        
                + sizeof(arp_packet->spa)         
                + sizeof(arp_packet->tha)        
                + sizeof(arp_packet->tpa);
  /*
   *  Copy the Ethernet frame header to the buffer.
   */
  memcpy(cp, &(frameHeader->dest_addr), sizeof(frameHeader->dest_addr));
  cp += sizeof(frameHeader->dest_addr);

  memcpy(cp, &(frameHeader->src_addr), sizeof(frameHeader->src_addr));
  cp += sizeof(frameHeader->src_addr);

  /* Normal Ethernet-II framing */
  memcpy(cp, &(frameHeader->frame_type), sizeof(frameHeader->frame_type));
  cp += sizeof(frameHeader->frame_type);


  /*
   *  Add the ARP data.
   */
  memcpy(cp, &(arp_packet->htype), sizeof(arp_packet->htype));
  cp += sizeof(arp_packet->htype);

  memcpy(cp, &(arp_packet->ptype), sizeof(arp_packet->ptype));
  cp += sizeof(arp_packet->ptype);

  memcpy(cp, &(arp_packet->hlen), sizeof(arp_packet->hlen));
  cp += sizeof(arp_packet->hlen);

  memcpy(cp, &(arp_packet->plen), sizeof(arp_packet->plen));
  cp += sizeof(arp_packet->plen);

  memcpy(cp, &(arp_packet->op), sizeof(arp_packet->op));
  cp += sizeof(arp_packet->op);

  memcpy(cp, &(arp_packet->sha), sizeof(arp_packet->sha));
  cp += sizeof(arp_packet->sha);

  memcpy(cp, &(arp_packet->spa), sizeof(arp_packet->spa));
  cp += sizeof(arp_packet->spa);

  memcpy(cp, &(arp_packet->tha), sizeof(arp_packet->tha));
  cp += sizeof(arp_packet->tha);

  memcpy(cp, &(arp_packet->tpa), sizeof(arp_packet->tpa));
  cp += sizeof(arp_packet->tpa);

  *bufferSize = packet_size;
}

Is this the correct way of structuring an ARP request packet?

1

There are 1 answers

2
Gil Hamilton On BEST ANSWER

That's the correct structure -- except that the C compiler is free to insert padding in order to ensure structure members are placed at the most efficient boundaries. In particular, spa and tpa are not at natural 32-bit boundaries (due to the preceding 6-byte MAC address fields) and so the compiler might want to insert two bytes of padding before each.

If you are using gcc, you can ensure that doesn't happen with __attribute__((packed)):

struct {
       [fields]
}  __attribute__((packed)) arp_ether_ipv4;

Other compilers might have a different but equivalent mechanism (a #pragma directive for example).

The ARP payload should be 28 bytes. Adding the 14-byte ethernet header, that gives 42 total bytes. As your cite said, an 802.1Q (VLAN) header inserts an additional 4 bytes and a "double-tagged" frame (not common outside of Internet service providers) will add 2 X 4 = 8 bytes. If you're on an ordinary endpoint machine, you wouldn't typically add these headers anyway. The IT department will have configured your switches to automatically insert/remove these headers as needed.

The 42 bytes will get padded automatically to 64 bytes (Ethernet minimum packet size) by your network driver. 64 is actually 60 + the 4-byte Ethernet FCS [frame checksum]. (The post you cited is apparently including the 4-byte FCS in their calculations, which is why their numbers seem whack.)

Also, don't forget to use network byte order for all uint16_t and uint32_t fields: (ntohs and ntohl)