Understanding ARP basics and sending ARP request and receiving ARP Reply using C code in Linux

The MAC address is how machines on a subnet communicate. When machine A sends packets to another machine on its subnet, it sends it using the MAC address. When sending a packet to a machine on the public Internet, the packet is sent to the MAC address of the router interface that is the default gateway. IP addresses are used to figure out the MAC address to send to using ARP.

ARP stands for Address Resolution Protocol. When you try to ping an IP address on your local network, say 192.168.1.1, your system has to turn the IP address 192.168.1.1 into a MAC address. This involves using ARP to resolve the address, hence its name.

Systems keep an ARP look-up table where they store information about what IP addresses are associated with what MAC addresses. When trying to send a packet to an IP address, the system will first consult this table to see if it already knows the MAC address. If there is a value cached, ARP is not used.

If the IP address is not found in the ARP table, the system will then send a broadcast packet to the network using the ARP protocol to ask “who has 192.168.1.1”. Because it is a broadcast packet, it is sent to a special MAC address ( ff:ff:ff:ff:ff:ff ) that causes all machines on the network to receive it. Any machine with the requested IP address will reply with an ARP packet that says “I am 192.168.1.1”, and this includes the MAC address which can receive packets for that IP.

Now, lets broadcasting an ARP reply packet which is not in response to a specific request. [ i.e. purposeful ARP reply without any request ] to understand. For this we can use below command,

 $ arping -A -I wlan0 192.168.0.106 

replace wlan0 as your active interface and 192.168.0.106 as with your active IP address.

This will send “ARP Reply” packets to the broadcast MAC address on wlan0, over and over until you press Control-C. Other machines on the network will update their ARP tables when they see these packets.

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 <linux/if_arp.h>
// 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))) < 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<5; i++) { printf ("%02x:", ether_frame[i]); } printf ("%02x\n", ether_frame[5]); printf ("Source MAC: "); for (i=0; i<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]) << 8) + ether_frame[13]); printf ("\nEthernet data (ARP header):\n"); printf ("Hardware type (1 = ethernet (10 Mb)): %u\n", ntohs (arphdr->htype)); printf ("Protocol type (2048 for IPv4 addresses): %u\n", ntohs (arphdr->ptype)); printf ("Hardware (MAC) address length (bytes): %u\n", arphdr->hlen); printf ("Protocol (IPv4) address length (bytes): %u\n", arphdr->plen); printf ("Opcode (2 = ARP reply): %u\n", ntohs (arphdr->opcode)); printf ("Sender hardware (MAC) address: "); for (i=0; i<5; i++) { printf ("%02x:", arphdr->sender_mac[i]); } printf ("%02x\n", arphdr->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<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 <= 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 <http://www.gnu.org/licenses/>.
*/
// 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 <linux/if_arp.h>
// 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)) < 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) < 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 (&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, &arphdr, ARP_HDRLEN * sizeof (uint8_t)); // Submit request for a raw socket descriptor. if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 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

Leave a Reply

Your email address will not be published. Required fields are marked *