The Address Resolution Protocol - Linux

The ARP protocol maps IP addresses to link layer addresses [RFC 826]. It is defined for the Ethernet Local Area Network (LAN). When a packet is about to be transmitted, the output routine prepends the link layer header to the output packet. The host contains a lookup table called the ARP cache that contains the mapping of the destination IP address and the link layer destination hardware address. When a lookup in the ARP cache fails, an ARP request message is broadcast on the local area net. When an ARP response message is received, the sender will update its ARP cache.

Any implementation of TCP/IP needs both an ARP cache and a routing table. Some systems implement a separate unique table for the ARP cache that doesn’t share any of the structure with the routing table. In Linux, ARP is implemented in file linux/net/ipv4/arp.c. It uses the generic neighbor cache facility, as the framework for the ARP cache. Some of this framework is shared with the routing cache implementation. Most of the ARP table management is supplied by the neighbor facility, and most of the generic framework for the neighbor cache . However, in this section, we will show the parts that are specific to the ARP protocol. We show how the ARP-specific parts are initialized, how ARP processes incoming packets, and how it handles requests to resolve addresses.

ARP Protocol Initialization

The ARP protocol has a few structures that are initialized at compile time in the file linux /net /ipv4 /arp. The first of these is the packet_type structure, which registers the handler function, arp_rcv, which will be called when an ARP packet arrives at any network interface device.

Another structure is the net device notifier block for the ARP protocol, arp_netdev_notifier.

The function arp_netdev_event will be called with any change in device status. The ARP protocol is initialized by the function arp_init implemented in file linux/net/ipv4/arp.c. This function is called as part of the main IP initialization process.

void __init arp_init(void) {

The ARP cache is initialized by calling neigh_table_init. ARP uses the neighbor facility, and neigh_table_init is the neighbor cache initialization function. Here we are creating a neighbor table instance. The neig_table instance for ARP, arp_tbl, is initialized at compile time and is shown later in this section.


The ARP protocol is not encapsulated with an IP packet. It is encapsulated directly by the link layer header; therefore, we must call dev_add_pack to register our packet type with the queuing layer. As shown earlier, the handler function points to arp_rcv, which will be called when a packet of type ETH_P_ARP is received at the network interface


We call arp_proc_init to initialize the ARP "directory" in the /proc pseudo file-system. This is so users can look at the ARP cache and tuning parameters.


There are many ARP parameters that can be tuned. We call the following function so that the ARP parameters can be set via sysctl if it is configured into the kernel.

Next, we register with the netdevice notifier so ARP will be informed when the interface address changes. When an interface address changes, the function arp_netdev_event will be called.
We will see later that we are only interested in the NETDEV_CHANGEADDR notification. We don’t use the notification of an interface going down. Instead,the function arp_ifdown is called directly to tell about this event.

Now let’s look at how the ARP cache is instantiated. As discussed earlier, Linux uses the neighbor cache as the framework for the ARP cache. Next, we show how a neigh_table structure instance, arp_table is initialized at compile time. This structure is the actual ARP cache.

The neigh_table has a destructor function defined but it is used for ARP. This is because the ARP protocol is a permanent part of IPv4, therefore the ARP cache will never totally be removed.

Here is the initialization of the neigh_parms values that contain the timeout values and the garbage collection intervals.

There are also four neighbor operations structures initialized for ARP, arp_ generic_ ops, arp_ hh_ ops, arp_ direct_ ops, and arp_ broken_ arps. Arp_ generic_ ops contain the operation functions called when we actually must resolve an address. Arp_ generic_ ops is initialized as follows.

static struct neigh_ops arp_generic_ops = { .family = AF_INET,

Only the solicit and error_report fields are set to functions unique to ARP. The other fields use the default ones for the neighbor cache.

The arp_hh_ops structure is initialized as shown here. Generally, this set of operations is used with Ethernet.

Again, the solicit and error_report functions are unique to ARP. The dev_queue_xmit function will queue the packet for transmission out of a network interface device. It will be called once a neighbor entry is resolved to an actual Ethernet address. The other operations are set to the generic ones for the neighbor cache.

The arp_direct_ops are used for a directly connected host that requires no hardware address, such as a point-to-point link. This is why all the functions point to dev_queue_xmit.

Arp_broken_ops is used for unreachable destinations.

struct neigh_ops arp_broken_ops = {

Receiving and Processing ARP Packets

The function arp_rcv implemented in the file linux/net/ipv4/arp.c is called when an ARP packet arrives at an Ethernet or other interface that supports ARP.

First, we make sure that the incoming packet is at least long enough to hold the ARP header. If not, we free the packet and get out.

In this function, we check to make sure that the interface through which the packet arrived uses the ARP protocol and that the packet is not a loopback packet.

Next, we check to make sure that the incoming packet is not shared. This would be the case if another protocol had received the packet and was modifying it. This is unlikely, but possible.

If all is OK, we call arp_process through the net filter.

The next function, arp_process, also implemented in the file linux/net/ipv4/arp.c, handles all arp_request packets. It is called from the low-level packet handler function arp_rcv.

Arp points to the ARP header in the incoming packet.

Sha points to the source hardware address, and tha points to the target hardware address.

unsigned char *sha, *tha;

Sip and Tip are for the source IP and destination IP addresses specified in the ARP packet.

First, we verify the packet header and make sure that the incoming network interface is an ARPtype interface. We allow for ARP packets from Ethernet, Token Ring, and FDDI.

This switch contains cases for each of the devices that are defined to support ARP.

In addition, we check for IEEE 802-type headers.

If the framing is IEEE 802 type framing, we accept hardware types of both ARPHRD_ETHER, one, and ARPHRD_IEEE802, six.

This is also true for Fibre Channel (FDDI) [RFC 2625]. The ARP hardware types are defined in file linux/include/linux/if_arp.h.

We only accept ARP request and ARP reply type messages. This is OK because there really aren’t any other ARP message types.

Next, we extract some fields from the ARP header.

Now we check for strange ARP requests such as requests for the loopback address or multicast addresses.

if (LOOPBACK(tip) || MULTICAST(tip)) goto out;

This is a special case where we must set the Frame Relay (FR) source address.

if (dev_type == ARPHRD_DLCI) sha = dev->broadcast;

At this point, we handle ARP request packets first. First, we handle the special case for duplicate address detection. We check to see if the source IP address in the request packet is zero and the target IP address is one of our local addresses. If so, we can send the response right away.

Now we handle the general case for the ARP request message type. We validate the address by checking to make sure that we have a route to the address being requested.

Neigh_event_ns does a lookup in the ARP cache for the source hardware address. If found, it updates the neighbor cache entry.

Here is where we send out the ARP reply message if, of course, we found the entry in the ARP cache.

This is a check to see if we are receiving a proxy ARP request. We check to see if the input device is configured for IP forwarding, and the route is a NAT route. The function arp_fwd_proxy is a check to see if we can use proxy ARP for a particular route. Rt points to the route cache entry for this packet.

Now, we timestamp the incoming packet.

Here we send out the ARP reply packet.

Pneigh_enqueue puts skb on the proxy queue and starts the timer in the arp_tbl.

Now we look for an entry in the ARP table matching the address and the network interface device. The zero in the last argument says to not create a new entry.

n =__neigh_lookup(&arp_tbl,&sip,dev, 0);

If we are configured for accepting unsolicited ARPs, we process them. Unsolicited ARPs are not accepted by default.

If n is not NULL, it means that it found an existing entry in the ARP table.

if (n) {

We set the NUD state to reachable. All reachable nodes can have an entry in the ARP cache.

Override is set to one if we want to override the existing cache entry with a new one. We try to use the first ARP reply if we have received several back to back.

If the ARP reply was a broadcast, or the incoming packet is not a reply packet, it means that the NUD state for the destination should not be flagged as reachable. We mark it as stale.

Finally, we call neigh_update to update the neighbor cache entry. Neigh_release decrements the use count in the neighbor cache entry because we are no longer operating on this entry.

All rights reserved © 2020 Wisdom IT Services India Pvt. Ltd Protection Status

Linux Topics