Sending ARP request and receiving ARP Reply using C code

To visualise what ARP reply we are getting from arping command, we need to write an ARP reply receiver code as below,

 $ vim receive_arp_reply.c 
/*  Copyright (C) 2011-2015  P.D. Buchan (pdbuchan@yahoo.com)
  
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
  
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
  
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
  
// Receive an ARP reply and extract the MAC address
// of the sender of the ARP reply, as well as any other
// information stored in the ethernet frame.
  
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>           // close()
#include <string.h>           // strcpy, memset()
  
#include <netinet/ip.h>       // IP_MAXPACKET (65535)
#include <sys/types.h>        // needed for socket(), uint8_t, uint16_t
#include <sys/socket.h>       // needed for socket()
#include <linux/if_ether.h>   // ETH_P_ARP = 0x0806, ETH_P_ALL = 0x0003
#include <net/ethernet.h>
  
#include <errno.h>            // errno, perror()
  
// Define an struct for ARP header
typedef struct _arp_hdr arp_hdr;
struct _arp_hdr {
  uint16_t htype;
  uint16_t ptype;
  uint8_t hlen;
  uint8_t plen;
  uint16_t opcode;
  uint8_t sender_mac[6];
  uint8_t sender_ip[4];
  uint8_t target_mac[6];
  uint8_t target_ip[4];
};
  
// Define some constants.
#define ARPOP_REPLY 2         // Taken from &lt;linux/if_arp.h&gt;
  
// Function prototypes
uint8_t *allocate_ustrmem (int);
  
int
main (int argc, char **argv)
{
  int i, sd, status;
  uint8_t *ether_frame;
  arp_hdr *arphdr;
  
  // Allocate memory for various arrays.
  ether_frame = allocate_ustrmem (IP_MAXPACKET);
  
  // Submit request for a raw socket descriptor.
  if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) &lt; 0) {
    perror ("socket() failed ");
    exit (EXIT_FAILURE);
  }
  
  // Listen for incoming ethernet frame from socket sd.
  // We expect an ARP ethernet frame of the form:
  //     MAC (6 bytes) + MAC (6 bytes) + ethernet type (2 bytes)
  //     + ethernet data (ARP header) (28 bytes)
  // Keep at it until we get an ARP reply.
  arphdr = (arp_hdr *) (ether_frame + 6 + 6 + 2);
  while (((((ether_frame[12]) << 8) + ether_frame[13]) != ETH_P_ARP) || (ntohs (arphdr->opcode) != ARPOP_REPLY)) {
    if ((status = recv (sd, ether_frame, IP_MAXPACKET, 0)) < 0) {
      if (errno == EINTR) {
        memset (ether_frame, 0, IP_MAXPACKET * sizeof (uint8_t));
        continue;  // Something weird happened, but let's try again.
      } else {
        perror ("recv() failed:");
        exit (EXIT_FAILURE);
      }
    }
  }
  close (sd);
  
  // Print out contents of received ethernet frame.
  printf ("\nEthernet frame header:\n");
  printf ("Destination MAC (this node): ");
  for (i=0; i&lt;5; i++) {
    printf ("%02x:", ether_frame[i]);
  }
  printf ("%02x\n", ether_frame[5]);
  printf ("Source MAC: ");
  for (i=0; i&lt;5; i++) {
    printf ("%02x:", ether_frame[i+6]);
  }
  printf ("%02x\n", ether_frame[11]);
  // Next is ethernet type code (ETH_P_ARP for ARP).
  // http://www.iana.org/assignments/ethernet-numbers
  printf ("Ethernet type code (2054 = ARP): %u\n", ((ether_frame[12]) &lt;&lt; 8) + ether_frame[13]);
  printf ("\nEthernet data (ARP header):\n");
  printf ("Hardware type (1 = ethernet (10 Mb)): %u\n", ntohs (arphdr-&gt;htype));
  printf ("Protocol type (2048 for IPv4 addresses): %u\n", ntohs (arphdr-&gt;ptype));
  printf ("Hardware (MAC) address length (bytes): %u\n", arphdr-&gt;hlen);
  printf ("Protocol (IPv4) address length (bytes): %u\n", arphdr-&gt;plen);
  printf ("Opcode (2 = ARP reply): %u\n", ntohs (arphdr-&gt;opcode));
  printf ("Sender hardware (MAC) address: ");
  for (i=0; i<5; i++) {
    printf ("%02x:", arphdr->sender_mac[i]);
  }
  printf ("%02x\n", arphdr-&gt;sender_mac[5]);
  printf ("Sender protocol (IPv4) address: %u.%u.%u.%u\n",
    arphdr->sender_ip[0], arphdr->sender_ip[1], arphdr->sender_ip[2], arphdr->sender_ip[3]);
  printf ("Target (this node) hardware (MAC) address: ");
  for (i=0; i&lt;5; i++) {
    printf ("%02x:", arphdr->target_mac[i]);
  }
  printf ("%02x\n", arphdr->target_mac[5]);
  printf ("Target (this node) protocol (IPv4) address: %u.%u.%u.%u\n",
    arphdr->target_ip[0], arphdr->target_ip[1], arphdr->target_ip[2], arphdr->target_ip[3]);
  
  free (ether_frame);
  
  return (EXIT_SUCCESS);
}
  
// Allocate memory for an array of unsigned chars.
uint8_t *
allocate_ustrmem (int len)
{
  void *tmp;
  
  if (len &lt;= 0) {
    fprintf (stderr, "ERROR: Cannot allocate memory because len = %i in allocate_ustrmem().\n", len);
    exit (EXIT_FAILURE);
  }
  
  tmp = (uint8_t *) malloc (len * sizeof (uint8_t));
  if (tmp != NULL) {
    memset (tmp, 0, len * sizeof (uint8_t));
    return (tmp);
  } else {
    fprintf (stderr, "ERROR: Cannot allocate memory for array allocate_ustrmem().\n");
    exit (EXIT_FAILURE);
  }
}

compile this code as,

 $ gcc -o receive_arp_reply receive_arp_reply.c 
 $ sudo ./receive_arp_reply

Ethernet frame header:
Destination MAC (this node): ff:ff:ff:ff:ff:ff
Source MAC: 0d:61:77:62:cf:50
Ethernet type code (2054 = ARP): 2054

Ethernet data (ARP header):
Hardware type (1 = ethernet (10 Mb)): 1
Protocol type (2048 for IPv4 addresses): 2048
Hardware (MAC) address length (bytes): 6
Protocol (IPv4) address length (bytes): 4
Opcode (2 = ARP reply): 2
Sender hardware (MAC) address: 0d:61:77:62:cf:50
Sender protocol (IPv4) address: 192.168.0.106
Target (this node) hardware (MAC) address: 0d:61:77:62:cf:50
Target (this node) protocol (IPv4) address: 192.168.0.106

So, as we can see “arping” sent above ARP reply to broadcast destination mac ff:ff:ff:ff:ff:ff with its own MAC address as 0c:60:76:61:ce:49.

Now, lets try with the c code which will try to send ARQ request to certain IP address which details we want to know, for this ARP request the IP we want to know details of will send the ARP reply which we will print using receive_arp_reply.c code as,

First, start receive_arp_reply to receive ARP reply,

 $ sudo ./receive_arp_reply 

This will wait for the ARP reply, so now we will use below code to send ARP request as,

/*  Copyright (C) 2011-2015  P.D. Buchan (pdbuchan@yahoo.com)
  
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
  
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
  
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.
*/
  
// Send an IPv4 ARP packet via raw socket at the link layer (ethernet frame).
// Values set for ARP request.
  
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>           // close()
#include <string.h>           // strcpy, memset(), and memcpy()
  
#include <netdb.h>            // struct addrinfo
#include <sys/types.h>        // needed for socket(), uint8_t, uint16_t
#include <sys/socket.h>       // needed for socket()
#include <netinet/in.h>       // IPPROTO_RAW, INET_ADDRSTRLEN
#include <netinet/ip.h>       // IP_MAXPACKET (which is 65535)
#include <arpa/inet.h>        // inet_pton() and inet_ntop()
#include <sys/ioctl.h>        // macro ioctl is defined
#include <bits/ioctls.h>      // defines values for argument "request" of ioctl.
#include <net/if.h>           // struct ifreq
#include <linux/if_ether.h>   // ETH_P_ARP = 0x0806
#include <linux/if_packet.h>  // struct sockaddr_ll (see man 7 packet)
#include <net/ethernet.h>
  
#include <errno.h>            // errno, perror()
  
// Define a struct for ARP header
typedef struct _arp_hdr arp_hdr;
struct _arp_hdr {
  uint16_t htype;
  uint16_t ptype;
  uint8_t hlen;
  uint8_t plen;
  uint16_t opcode;
  uint8_t sender_mac[6];
  uint8_t sender_ip[4];
  uint8_t target_mac[6];
  uint8_t target_ip[4];
};
  
// Define some constants.
#define ETH_HDRLEN 14      // Ethernet header length
#define IP4_HDRLEN 20      // IPv4 header length
#define ARP_HDRLEN 28      // ARP header length
#define ARPOP_REQUEST 1    // Taken from &lt;linux/if_arp.h&gt;
  
// Function prototypes
char *allocate_strmem (int);
uint8_t *allocate_ustrmem (int);
  
int
main (int argc, char **argv)
{
  int i, status, frame_length, sd, bytes;
  char *interface, *target, *src_ip;
  arp_hdr arphdr;
  uint8_t *src_mac, *dst_mac, *ether_frame;
  struct addrinfo hints, *res;
  struct sockaddr_in *ipv4;
  struct sockaddr_ll device;
  struct ifreq ifr;
  
  // Allocate memory for various arrays.
  src_mac = allocate_ustrmem (6);
  dst_mac = allocate_ustrmem (6);
  ether_frame = allocate_ustrmem (IP_MAXPACKET);
  interface = allocate_strmem (40);
  target = allocate_strmem (40);
  src_ip = allocate_strmem (INET_ADDRSTRLEN);
  
  // Interface to send packet through.
  strcpy (interface, "wlan0");
  
  // Submit request for a socket descriptor to look up interface.
  if ((sd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) &lt; 0) {
    perror ("socket() failed to get socket descriptor for using ioctl() ");
    exit (EXIT_FAILURE);
  }
  
  // Use ioctl() to look up interface name and get its MAC address.
  memset (&ifr, 0, sizeof (ifr));
  snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface);
  if (ioctl (sd, SIOCGIFHWADDR, &ifr) &lt; 0) {
    perror ("ioctl() failed to get source MAC address ");
    return (EXIT_FAILURE);
  }
  close (sd);
  
  // Copy source MAC address.
  memcpy (src_mac, ifr.ifr_hwaddr.sa_data, 6 * sizeof (uint8_t));
  
  // Report source MAC address to stdout.
  printf ("MAC address for interface %s is ", interface);
  for (i=0; i<5; i++) {
    printf ("%02x:", src_mac[i]);
  }
  printf ("%02x\n", src_mac[5]);
  
  // Find interface index from interface name and store index in
  // struct sockaddr_ll device, which will be used as an argument of sendto().
  memset (&device, 0, sizeof (device));
  if ((device.sll_ifindex = if_nametoindex (interface)) == 0) {
    perror ("if_nametoindex() failed to obtain interface index ");
    exit (EXIT_FAILURE);
  }
  printf ("Index for interface %s is %i\n", interface, device.sll_ifindex);
  
  // Set destination MAC address: broadcast address
  memset (dst_mac, 0xff, 6 * sizeof (uint8_t));
  
  // Source IPv4 address:  you need to fill this out
  strcpy (src_ip, "192.168.0.106");
  
  // Destination URL or IPv4 address (must be a link-local node): you need to fill this out
  strcpy (target, "192.168.0.1");
  
  // Fill out hints for getaddrinfo().
  memset (&amp;hints, 0, sizeof (struct addrinfo));
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = hints.ai_flags | AI_CANONNAME;
  
  // Source IP address
  if ((status = inet_pton (AF_INET, src_ip, &arphdr.sender_ip)) != 1) {
    fprintf (stderr, "inet_pton() failed for source IP address.\nError message: %s", strerror (status));
    exit (EXIT_FAILURE);
  }
  
  // Resolve target using getaddrinfo().
  if ((status = getaddrinfo (target, NULL, &hints, &res)) != 0) {
    fprintf (stderr, "getaddrinfo() failed: %s\n", gai_strerror (status));
    exit (EXIT_FAILURE);
  }
  ipv4 = (struct sockaddr_in *) res->ai_addr;
  memcpy (&arphdr.target_ip, &ipv4->sin_addr, 4 * sizeof (uint8_t));
  freeaddrinfo (res);
  
  // Fill out sockaddr_ll.
  device.sll_family = AF_PACKET;
  memcpy (device.sll_addr, src_mac, 6 * sizeof (uint8_t));
  device.sll_halen = 6;
  
  // ARP header
  
  // Hardware type (16 bits): 1 for ethernet
  arphdr.htype = htons (1);
  
  // Protocol type (16 bits): 2048 for IP
  arphdr.ptype = htons (ETH_P_IP);
  
  // Hardware address length (8 bits): 6 bytes for MAC address
  arphdr.hlen = 6;
  
  // Protocol address length (8 bits): 4 bytes for IPv4 address
  arphdr.plen = 4;
  
  // OpCode: 1 for ARP request
  arphdr.opcode = htons (ARPOP_REQUEST);
  
  // Sender hardware address (48 bits): MAC address
  memcpy (&arphdr.sender_mac, src_mac, 6 * sizeof (uint8_t));
  
  // Sender protocol address (32 bits)
  // See getaddrinfo() resolution of src_ip.
  
  // Target hardware address (48 bits): zero, since we don't know it yet.
  memset (&arphdr.target_mac, 0, 6 * sizeof (uint8_t));
  
  // Target protocol address (32 bits)
  // See getaddrinfo() resolution of target.
  
  // Fill out ethernet frame header.
  
  // Ethernet frame length = ethernet header (MAC + MAC + ethernet type) + ethernet data (ARP header)
  frame_length = 6 + 6 + 2 + ARP_HDRLEN;
  
  // Destination and Source MAC addresses
  memcpy (ether_frame, dst_mac, 6 * sizeof (uint8_t));
  memcpy (ether_frame + 6, src_mac, 6 * sizeof (uint8_t));
  
  // Next is ethernet type code (ETH_P_ARP for ARP).
  // http://www.iana.org/assignments/ethernet-numbers
  ether_frame[12] = ETH_P_ARP / 256;
  ether_frame[13] = ETH_P_ARP % 256;
  
  // Next is ethernet frame data (ARP header).
  
  // ARP header
  memcpy (ether_frame + ETH_HDRLEN, &amp;arphdr, ARP_HDRLEN * sizeof (uint8_t));
  
  // Submit request for a raw socket descriptor.
  if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) &lt; 0) {
    perror ("socket() failed ");
    exit (EXIT_FAILURE);
  }
  
  // Send ethernet frame to socket.
  if ((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *) &device, sizeof (device))) <= 0) {
    perror ("sendto() failed");
    exit (EXIT_FAILURE);
  }
  
  // Close socket descriptor.
  close (sd);
  
  // Free allocated memory.
  free (src_mac);
  free (dst_mac);
  free (ether_frame);
  free (interface);
  free (target);
  free (src_ip);
  
  return (EXIT_SUCCESS);
}
  
// Allocate memory for an array of chars.
char *
allocate_strmem (int len)
{
  void *tmp;
  
  if (len <= 0) {
    fprintf (stderr, "ERROR: Cannot allocate memory because len = %i in allocate_strmem().\n", len);
    exit (EXIT_FAILURE);
  }
  
  tmp = (char *) malloc (len * sizeof (char));
  if (tmp != NULL) {
    memset (tmp, 0, len * sizeof (char));
    return (tmp);
  } else {
    fprintf (stderr, "ERROR: Cannot allocate memory for array allocate_strmem().\n");
    exit (EXIT_FAILURE);
  }
}
  
// Allocate memory for an array of unsigned chars.
uint8_t *
allocate_ustrmem (int len)
{
  void *tmp;
  
  if (len <= 0) {
    fprintf (stderr, "ERROR: Cannot allocate memory because len = %i in allocate_ustrmem().\n", len);
    exit (EXIT_FAILURE);
  }
  
  tmp = (uint8_t *) malloc (len * sizeof (uint8_t));
  if (tmp != NULL) {
    memset (tmp, 0, len * sizeof (uint8_t));
    return (tmp);
  } else {
    fprintf (stderr, "ERROR: Cannot allocate memory for array allocate_ustrmem().\n");
    exit (EXIT_FAILURE);
  }
}
$ gcc -o send_arp_request send_arp_request.c 
$ sudo ./send_arp_request 
MAC address for interface wlan0 is 0d:61:77:62:cf:50
Index for interface wlan0 is 3

Once we run the above program, it will send an ARP request to 192.168.0.1 as we hardcoded into program and for that request the machine which has IP address 192.168.0.1 will respond with its Mac address and the same reply we are receiving into receive_arp_reply which will print the output as below,

$ sudo ./receive_arp_reply 

Ethernet frame header:
Destination MAC (this node): 0d:61:77:62:cf:50
Source MAC: 65:71:03:e2:2d:17
Ethernet type code (2054 = ARP): 2054

Ethernet data (ARP header):
Hardware type (1 = ethernet (10 Mb)): 1
Protocol type (2048 for IPv4 addresses): 2048
Hardware (MAC) address length (bytes): 6
Protocol (IPv4) address length (bytes): 4
Opcode (2 = ARP reply): 2
Sender hardware (MAC) address: 65:71:03:e2:2d:17
Sender protocol (IPv4) address: 192.168.0.1
Target (this node) hardware (MAC) address: 0d:61:77:62:cf:50
Target (this node) protocol (IPv4) address: 192.168.0.106

Credit – https://www.tummy.com/articles/networking-basics-how-arp-works/ and http://www.pdbuchan.com/rawsock/rawsock.html

6 thoughts on “Sending ARP request and receiving ARP Reply using C code”

  1. Inside sending ARP request code, is it necessory to create socket with ETH_P_ALL (refer line number 205) as third parameter? I think it should be ETH_P_ARP

    Reply
  2. I did exactly just like above with ETH_P_ARP, but the receiver does not receive at all.
    Is there anything wrong with receiver side ?

    Reply
    • Please verify whether you are running client & server in same network / are reachable with each other .. Did you follow all above steps and still it didn’t worked ?

      Reply
      • The client and server program is run in the same host. First, I run the receiver in one terminal, and run the sender in another one and expect to see the result in the first terminal. The sender works fine and I checked using tcpdump. But the receiver is waiting in the line 76. So I changed `ntohs (arphdr->opcode) != ARPOP_REPLY` to ntohs (arphdr->opcode) == ARPOP_REPLY` because I think the receiver is waiting for ARP reply message. By doing this, I see arp reply messages but not the one I expect from sender.

        Reply
      • When I run send program and do tcpdump, I see something like this:
        10:21:30.542305 [|ARP]
        0x0000: c0a8 010a c0a8 c880 0001 0800 0604 0001 …………….
        0x0010: 000c 293a 81e6 0000 0000 0000 ..):……..

        but a normal arp request message looks like this:
        10:22:52.660130 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 192.168.200.2 tell 192.168.200.128, length 28

        I assume I have a problem in sending side.

        Reply

Leave a Comment