Network programming in Linux

Address Resolution Protocol (ARP)


Introduction
Getting started with libpcap

Extracting Ethernet information
Internet Protocol (IP)

Filtering captured datagrams
Capturing datagrams offline

Address Resolution Protocol (ARP)
Internet Control Message Protocol (ICMP)
Transmission Control Protocol (TCP)
User Datagram Protocol (UDP)
Trivial File Transfer Protocol (TFTP)

Injecting datagrams with libnet
Implementing ping
Implementing traceroute

Download source code


The Address Resolution Protocol, commonly known as ARP, is a link layer protocol for mapping Internet layer addresses, such as IPv4 32 bits addresses, into physical device addresses such as 48 bits Ethernet MAC addresses. A table, usually called the ARP cache, is used to maintain a correlation between each MAC address and its corresponding IP address. ARP provides the protocol rules for making this correlation and providing address conversion in both directions (i.e. IP → MAC and MAC → IP).

How ARP works

When an incoming IP packet destined for a host within the local network arrives at the network gateway, the gateway uses ARP to find the physical address (MAC) corresponding to the logical destination address (IP) embedded in the packet. The gateway's ARP cache is first checked and, if the target IP ↔ MAC pair is found, the IP packet is encapsulated within an Ethernet frame with the appropriate destination MAC. If no entry is found in the ARP cache with the target IP address, ARP broadcasts an ARP request packet to all hosts within the local network to see if one owns the target IP address. A host owning this IP address replies to the gateway (i.e. an ARP reply packet). The gateway updates is ARP cache with the new IP ↔ MAC pair for future references.

There is also a Reverse ARP protocol (RARP) for hosts that don't know their IP address. RARP enables them to request their IP address from the gateway's ARP cache. While ARP is commonly used in Ethernet-based local network, RARP seldom is.

The ARP packet format

ARP uses a simple message format containing one address resolution request or response. The size of the ARP message depends on the upper layer and lower layer address sizes, which are given by the type of networking protocol (usually IPv4) in use and the type of hardware link layer that the upper layer protocol is running on. The message header specifies these types, as well as the size of addresses for each. The message header is completed with an operation code which is either request (1) or reply (2). The packet's payload consists of four addresses: the hardware and protocol address of the sending and receiving hosts.

The following figure illustrates the ARP packet format, where each dark blue box represents a single byte. The total packet length depends on the length of the addresses within the header; in this figure hardware addresses are assumed to be MAC (6 bytes each) and protocol addresses are IPv4 (4 bytes each):

ARP packet

  • Hardware Type: This field specifies the network protocol type. For example, Ethernet is 0x0001.

  • Protocol Type: This field specifies the internetwork protocol for which the ARP request is intended. For IPv4, the value 0x0800.

  • Hardware address length: Length (in bytes) of a hardware address. Ethernet MAC addresses are 6 bytes long.

  • Protocol address length: Length (in bytes) of addresses used in the upper layer protocol. IPv4 address size is 4.

  • Operation: Specifies the operation that the sender is performing: 0x0001 for an ARP request, 0x0002 for an ARP reply.

  • Source hardware address: device address of the sender.

  • Source protocol address: network address of the sender.

  • Destination hardware address: device address of the receiver, or 00:00:00:00:00:00 if it's a request broadcast.

  • Destination protocol address: network address of the receiver.

ARP packets do not transport payload since they don't encapsulate higher protocols.

The ARPPacket class

The ARPPacket maps raw captured bytes (stored in a Datagram instance) into corresponding ARP header fields; it is therefore derived from the DatagramFragment class. The commented class definition is self-explanatory.

arppacket.h
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
#ifndef ARPPACKET_H
#define ARPPACKET_H

#include <iostream>
#include <stdio.h>

#include "datagramfragment.h"   // DatagramFragment
#include "macaddress.h"         // Mac
#include "ipaddress.h"          // IPAddress

using namespace std;

/* ARPPacket: class mapping the inherited data block as an ARP packet.
 *
 * Attributes
 *   p_data (inherited) : array of bytes
 *   p_len (inherited)  : size of p_data
 *
 * Notes
 *   1. the data block referenced by p_data may not be owned by the instance
 *      but instead owned by a Datagram instance which shares its data with
 *      instances of classes derived from DatagramFragment, including this
 *      class.
 */
class ARPPacket : public DatagramFragment {
  public:
    // Enumeration of hardware address types
    typedef enum {
      aht_Ethernet, aht_FrameRelay, aht_ATM, aht_IPSec, aht_unknown
    } ARPHardwareType;

    // Enumeration of protocol address types 
    typedef enum {
      apt_IPv4, apt_IPX, apt_802_1Q, apt_IPv6, apt_unknown
    } ARPProtocolType;

    // Enumeration of ARP operations 
    typedef enum {
      akt_ArpRequest, akt_ArpReply, akt_RarpRequest, akt_RarpReply, akt_unknown
    } ARPPacketType;

    ARPPacket(bool = false);                              // constructor
    ARPPacket(bool, unsigned char *, unsigned int);       // parameterized constructor
    
    unsigned int header_length() const;                   // length of ARP packet header in bytes
    
    // Routines returning header field values
    ARPPacketType operation() const;
    unsigned int operation_code() const;

    ARPHardwareType hardware_type() const;
    unsigned int hardware_type_code() const;
    
    ARPProtocolType protocol_type() const;
    unsigned int protocol_type_code() const;    

    unsigned int hardware_adr_length() const;
    unsigned int protocol_adr_length() const;

    // Returns hardware addresses found in the header
    MacAddress destination_mac() const;
    MacAddress source_mac() const;
    
    // Returns protocol addresses found in the header
    IPAddress destination_ip() const;
    IPAddress source_ip() const;
    
    // Operator overloads
    friend ostream & operator<<(ostream &, const ARPPacket &);
    
    protected:
};

#endif

And here are the member definitions of the ARPPacket class:

arppacket.cpp
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#ifndef ARPPACKET_CPP
#define ARPPACKET_CPP

#include "arppacket.h"   // ARPPacket
#include "exceptions.h"  // EBadTransportException

// Default constructor
ARPPacket::ARPPacket(bool owned) : DatagramFragment(owned) {
}

// Parameterized constructor
ARPPacket::ARPPacket(bool owned, unsigned char * s, unsigned int l) : DatagramFragment(owned, s, l) {
}

// Returns the IP header length in bytes
unsigned int ARPPacket::header_length() const {
  if (!p_data) 
    return 0;
  else
    return hardware_adr_length() * 2 + protocol_adr_length() * 2 + 8;
}
    
// Returns the destination hardware address field's content
MacAddress ARPPacket::destination_mac() const {
  if (hardware_type() == ARPPacket::aht_Ethernet)
    return MacAddress(false, p_data + 8 + hardware_adr_length() + protocol_adr_length());
  else
    throw EBadHardwareException("Hardware layer not Ethernet based");
}

// Returns the source hardware address field's content
MacAddress ARPPacket::source_mac() const {
  if (hardware_type() == ARPPacket::aht_Ethernet)
    return MacAddress(false, p_data + 8);
  else
    throw EBadHardwareException("Hardware layer not Ethernet based");
}

// Returns the destination protocol address field's content
IPAddress ARPPacket::destination_ip() const {
  if (protocol_type() == ARPPacket::apt_IPv4)
    return IPAddress(false, p_data + 8 + hardware_adr_length() * 2 + protocol_adr_length());
  else
    throw EBadTransportException("Protocol layer not IPv4 based");
}

// Returns the source protocol address field's content
IPAddress ARPPacket::source_ip() const {
  if (protocol_type() == ARPPacket::apt_IPv4)
    return IPAddress(false, p_data + 8 + hardware_adr_length());
  else
    throw EBadTransportException("Protocol layer not IPv4 based");
}

// Indicates what ARP operation the packet transports 
ARPPacket::ARPPacketType ARPPacket::operation() const {
  switch (operation_code()) {
    case 0x0001 : return akt_ArpRequest;
    case 0x0002 : return akt_ArpReply;
    case 0x0003 : return akt_RarpRequest;
    case 0x0004 : return akt_RarpReply;
    default     : return akt_unknown;
  }
}

// Indicates what ARP operation the packet transports 
unsigned int ARPPacket::operation_code() const {
  if (p_data) 
    return char2word(p_data+6);    // operation code stored in two bytes
  else
    return 0;
}

// Indicates the hardware layer type
ARPPacket::ARPHardwareType ARPPacket::hardware_type() const {
  switch (hardware_type_code()) {
    case 0x0001 : return aht_Ethernet;
    case 0x000F : return aht_FrameRelay;
    case 0x0010 : 
    case 0x0013 : 
    case 0x0015 : return aht_ATM;
    case 0x001F : return aht_IPSec;
    default     : return aht_unknown;
  }
}

// Indicates the hardware layer type
unsigned int ARPPacket::hardware_type_code() const {
  if (p_data) 
    return char2word(p_data+0);    // hardware layer type stored in two bytes
  else
    return 0;
}

// Indicates the protocol layer type
ARPPacket::ARPProtocolType ARPPacket::protocol_type() const {
  switch (protocol_type_code()) {
    case 0x0800 : return apt_IPv4;
    case 0x8037 : return apt_IPX;
    case 0x8100 : return apt_802_1Q;
    case 0x86DD : return apt_IPv6;
    default     : return apt_unknown;
  }
}

// Indicates the protocol layer type
unsigned int ARPPacket::protocol_type_code() const {
  if (p_data) 
    return char2word(p_data+2);    // protocol layer type stored in two bytes
  else
    return 0;
}

// Returns hardware addresses length in bytes
unsigned int ARPPacket::hardware_adr_length() const {
  if (p_data)
    return p_data[4];
  else
    return 0;
}

// Returns protocol addresses length in bytes
unsigned int ARPPacket::protocol_adr_length() const {
  if (p_data)
    return p_data[5];
  else
    return 0;
}

// Output operator displaying the ARP packet header fields in human readable
// form
ostream & operator<<(ostream & ostr, const ARPPacket & arp) {
  if (arp.p_data) {
    char outstr[7];
        
    // Get operation code
    sprintf(outstr, "0x%.4x", arp.operation_code());

    // Display operation textually along its corresponding code
    ostr << "packet type = ";
    switch (arp.operation()) {
      case ARPPacket::akt_ArpRequest  : ostr << "ARP request ["  << outstr << "]" << endl; break;
      case ARPPacket::akt_ArpReply    : ostr << "ARP reply ["    << outstr << "]" << endl; break;
      case ARPPacket::akt_RarpRequest : ostr << "RARP request [" << outstr << "]" << endl; break;
      case ARPPacket::akt_RarpReply   : ostr << "RARP reply ["   << outstr << "]" << endl; break;
      default                         : ostr << "unknown ["      << outstr << "]" << endl; break;
    }
            
    // Display hardware addresses
    ostr << "destination MAC address = " << arp.destination_mac() << endl;
    ostr << "source MAC address = " << arp.source_mac() << endl;
        
    // Display protocol addresses
    ostr << "destination IP address = " << arp.destination_ip() << endl;
    ostr << "source IP address = " << arp.source_ip() << endl;
        
    // Get hardware type
    sprintf(outstr, "0x%.4x", arp.hardware_type_code());

    // Display hardware type textually along ints corresponding code
    ostr << "hardware type = ";
    switch (arp.hardware_type()) {
      case ARPPacket::aht_Ethernet   : ostr << "Ethernet ["                             
                                            << outstr << "]" << endl; break;
      case ARPPacket::aht_FrameRelay : ostr << "Frame Relay ["                          
                                            << outstr << "]" << endl; break;
      case ARPPacket::aht_ATM        : ostr << "Asynchronous Transmission Mode (ATM) [" 
                                            << outstr << "]" << endl; break;
      case ARPPacket::aht_IPSec      : ostr << "IPSec tunnel ["                         
                                            << outstr << "]" << endl; break;
      default                        : ostr << "unknown ["                              
                                            << outstr << "]" << endl; break;
    }
            
    // Get protocol type
    sprintf(outstr, "0x%.4x", arp.protocol_type_code());

    // Display protocol type textually along ints corresponding code
    ostr << "protocol type = ";
    switch (arp.protocol_type()) {
      case ARPPacket::apt_IPv4   : ostr << "IPv4 ["         << outstr << "]" << endl; break;
      case ARPPacket::apt_IPX    : ostr << "IPX ["          << outstr << "]" << endl; break;
      case ARPPacket::apt_802_1Q : ostr << "IEEE 802.1Q) [" << outstr << "]" << endl; break;
      case ARPPacket::apt_IPv6   : ostr << "IPv6 ["         << outstr << "]" << endl; break;
      default                    : ostr << "unknown ["      << outstr << "]" << endl; break;
    }         
  }
    
  ostr << flush;
    
  return ostr;
}

#endif

Let's have a closer look at ARPPacket's implementation:

  • The class defines three enumerations for, respectively, hardware address types, protocol address types and operations.

  • The routines returning hardware addresses found within the packet header (at lines #024 and #032) only recognize Ethernet based MAC addresses. An exception is thrown for any other address type.

  • The routines returning protocol addresses found within the packet header (at lines #040 and #048) only recognize IPv4 addresses. Again, an exception is thrown for any other address type.

Next, we add a new method to the EtherFrame class for mapping its payload into an ARPPacket instance:

ethernetframe.cpp (partial)
124
125
126
127
128
129
130
// Returns an instance of the ARP datagram transported as payload
IPPacket EthernetFrame::arp() {
    if (ether_type() != et_arp)   // make sure it transports ARP
        throw EBadTransportException("Ethernet frame not transporting ARP traffic");

    return ARPPPacket(false, data(), length() - header_length());
}

Finally, we update the main program's callback function to map the captured bytes into an ARPPacket instance for display:

sniff09.cpp (partial)
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
// Callback given to pcap_loop() for processing captured datagrams
void process_packet(u_char *user, const struct pcap_pkthdr * h, const u_char * packet) {
  IPPacket ip;
  ARPPacket arp;
  
  cout << "Grabbed " << h->caplen << " bytes (" << static_cast<int>(100.0 * h->caplen / h->len) 
       << "%) of datagram received on " << ctime((const time_t*)&h->ts.tv_sec);
       
  Datagram pkt(packet, h->caplen);        // initialized Datagram instance
  if (show_raw) cout << "---------------- Raw data -----------------" << pkt << endl;

  EthernetFrame ether = pkt.ethernet();   // get EthernetFrame instance from transported data
  cout << "---------- Ethernet frame header ----------" << endl << ether;
  
  // Display payload content according to EtherType
  switch (ether.ether_type()) {
    case EthernetFrame::et_IPv4 :         // get IPPacket instance from transported data
      ip = ether.ip4();
      cout << "-------- IP packet header --------" << endl << ip;
      break;

    case EthernetFrame::et_ARP :          // get ARPPacket instance from transported data
      arp = ether.arp();
      cout << "-------- ARP packet header --------" << endl << arp;
      break;
  }
      
  cout << endl << flush;

  // Log datagram if required
  if (user != NULL)
    pcap_dump(user, h, packet);
}

Here is the result of capturing an ARP request packet followed by the resulting ARP reply packet:

%root> ./sniff09
device = eth0
network ip = 172.16.179.0
network mask = 255.255.255.0
Grabbed 42 bytes (100%) of datagram received on Mon Sep 16 14:54:42 2013
---------- Ethernet frame header ----------
destination MAC address = 00.50.56.ec.28.a3
source MAC address = 00.0c.29.19.22.3a
ether type = ARP [0x0806]
-------- ARP packet header --------
packet type = ARP request [0x0001]
destination MAC address = 00.00.00.00.00.00
source MAC address = 00.0c.29.19.22.3a
destination IP address = 172.16.179.2
source IP address = 172.16.179.135
hardware type = Ethernet [0x0001]
protocol type = IPv4 [0x0800]

Grabbed 60 bytes (100%) of datagram received on Mon Sep 16 14:54:42 2013
---------- Ethernet frame header ----------
destination MAC address = 00.0c.29.19.22.3a
source MAC address = 00.50.56.ec.28.a3
ether type = ARP [0x0806]
-------- ARP packet header --------
packet type = ARP reply [0x0002]
destination MAC address = 00.0c.29.19.22.3a
source MAC address = 00.50.56.ec.28.a3
destination IP address = 172.16.179.135
source IP address = 172.16.179.2
hardware type = Ethernet [0x0001]
protocol type = IPv4 [0x0800]

^C
*** Capture process interrupted by user...
%root>

Note in the above capture session the ARP request packet's destination MAC is 00:00:00:00:00:00, indicating that host 00.0c.29.19.22.3a seeks the MAC of the device who's host IP is 172.16.179.2. The target host replies in the following ARP packet with its MAC, 00.50.56.ec.28.a3. All other hosts in the local network ignore the ARP request since it does not target their IP address.

Application - Detecting ARP spoofing

ARP spoofing is a technique whereby an attacker sends fake (i.e. "spoofed") ARP messages in a local network. Generally, the goal is to associate the attacker's MAC address with the IP address of another host (such as the default gateway), causing any traffic meant for latter to be sent to the former. ARP spoofing may allow an attacker to intercept data link frames on a LAN, modify the traffic, or stop the traffic altogether. Spoofing is often used as first phase of larger attacks such as denial of service, man in the middle, or session hijacking attacks.

ARP is a stateless protocol: hosts will automatically cache all ARP replies they receive, regardless of whether or not they requested them. Even existing ARP cache entries will be overwritten when a conflicting ARP reply packet is received. There is no method in ARP protocol by which a host can authenticate the peer from which the packet originated. This behavior is the vulnerability exploited in ARP spoofing.

The basic idea behind ARP spoofing is to send spoofed ARP messages into the local network. Generally, the goal of the attack is to associate the attacker's MAC address with the IP address of a target host, so that any traffic meant for the target host will be sent to the attacker's MAC instead. To do so, the attacker will periodically send unsolicited ARP replies in the network.

The best defense against ARP spoofing is to disable ARP itself, statically programming ARP entries in the cache of all hosts so that ARP requests and replies become unnecessary. However, while static entries provide perfect security against spoofing, they result in quadratic maintenance efforts as IP-MAC mappings of all hosts in the network have to be distributed to all other hosts. Such solution is therefore inapplicable but for very small networks.

Since disabling the ARP protocol is not applicable in most cases, the next best defense against ARP spoofing is detection. The strategy underlying the detection of ARP spoofing is to track ARP packets in order to identify unsolicited ARP replies, i.e. captured ARP replies for which no corresponding ARP requests have been captured. The application must therefore keep track of "opened" (i.e. unanswered) ARP requests , and match ARP replies to them. Any unmatched ARP reply is therefore suspicious and flagged as a potential spoof.

The following enhanced sniffer implements ARP spoofing detection as described above. All required code modifications are limited to the file containing the main() routine.

sniff10.cpp (partial)



045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065



074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146



162
163
164



207
208
209



215
216
217
218
219
220
221
222
223
224



315
316
317
318
319

...

unsigned int capture_count = 0;       // count of captured datagrams

// Function releasing all resources before ending program execution
void shutdown(int error_code) {
  // Close log file
  if (logfile != NULL)
    pcap_dump_close(logfile);

  // Destroy compiled BPF filter if need be
  if (strfilter != NULL)
      pcap_freecode(&binfilter);

  // Close libpcap session
  if (pcap_session != NULL)
    pcap_close(pcap_session);

  // Display the total number of datagrams captured
  cout << "*** " << capture_count << " datagrams captured" << endl;
  
  exit(error_code); // we're done!
}

...

bool show_raw   = false;          // deactivate raw display of data captured
bool quiet_mode = false;          // controls whether the callback display captured datagrams or not
int  security_tool = 0;           // security tool to apply

#define ARPSPOOF 1

// Macro replacing cout to apply conditional display in callback
#define COUT if (!quiet_mode) cout

// Callback given to pcap_loop() for processing captured datagrams
void process_packet(u_char *user, const struct pcap_pkthdr * h, const u_char * packet) {
  static set<IPAddress> arpRequests;
  IPPacket ip;
  ARPPacket arp;
  
  COUT << "Grabbed " << h->caplen << " bytes (" << static_cast<int>(100.0 * h->caplen / h->len) 
       << "%) of datagram received on " << ctime((const time_t*)&h->ts.tv_sec);
       
  Datagram pkt(packet, h->caplen);        // initialized Datagram instance
  if (show_raw) COUT << "---------------- Raw data -----------------" << pkt << endl;

  EthernetFrame ether = pkt.ethernet();   // get EthernetFrame instance from transported data
  COUT << "---------- Ethernet frame header ----------" << endl << ether;
  
  // Display payload content according to EtherType
  switch (ether.ether_type()) {
    case EthernetFrame::et_IPv4 :         // get IPPacket instance from transported data
      ip = ether.ip4();
      COUT << "-------- IP packet header --------" << endl << ip;
      break;

    case EthernetFrame::et_ARP :          // get ARPPacket instance from transported data
      arp = ether.arp();
      COUT << "-------- ARP packet header --------" << endl << arp;
      
      // Check if we must apply ARP spoofing detection
      if (security_tool == ARPSPOOF) {
        switch (arp.operation()) {
          case ARPPacket::akt_ArpRequest:
            // Add target's IP to the set to log there was a request for its MAC
            arpRequests.insert(arp.destination_ip());
            break;
            
          case ARPPacket::akt_ArpReply:
            // Make sure the source respond to a legitimate request
            set<IPAddress>::iterator it = arpRequests.find(arp.source_ip());
            if (it == arpRequests.end())
              // This reply is gratuitous (no corresponding request)
              cout << endl <<  "**** ALERT - Potential ARP spoofing detected ****" << endl
                           << "     unsollicited ARP reply to " << arp.destination_mac()
                           << "     originating from " << arp.source_mac() << endl << endl;
            else
              arpRequests.erase(it);  // remove from set to indicate the request was replied
              
            break;
        }
      }
      
      break;
  }
      
  COUT << endl << flush;

  // Log datagram if required
  if (user != NULL)
    pcap_dump(user, h, packet);
    
  // Count the capture
  capture_count++;
}

// Sniffer's main program: add ARP spoofing detection
int main(int argc, char *argv[]) {

  ...

  // Process command line arguments
  while ((argch = getopt(argc, argv, "hpqrd:f:i:l:n:s:")) != EOF)
    switch (argch) {
    
      ...
      
      case 'q':           // active quiet mode
        quiet_mode = 1;
        break;
        
      ...

      case 's':           // apply specified security tool
        if (string(optarg) == "arpspoof") 
          security_tool = ARPSPOOF;
        else {
          cerr << "error - unknow security tool specified (" << optarg << ")" << endl;
          return -10;
        }
          
        break;
    }

  ...
  
  // Display any security application enabled
  switch (security_tool) {
    case ARPSPOOF: cout << "arp spoofing detection enabled..." << endl;
                   break;
  }
  
  ...

Here are the enhancements implementing ARP spoofing detection in sniffer:

  • A new command line argument (-q at line #207, enabling the global flag quiet_mode) is introduced to make available a quiet mode preventing the display of each captured datagram. This option is necessary to prevent saturating the display with captured data, which might cause security alerts to go unnoticed. To implement quiet mode, a macro is defined (COUT at line #081) that makes the cout output stream conditional when displaying captured datagrams in the callback function process_packet() at lines #089, #093, #096, #102, #107 and #135. Furthermore, to give users an insight to successful captures when quiet mode is enabled, the global variable capture_count (defined at line #045) is incremented at each capture (line #142) and displayed upon termination (line #062).

  • Another command line argument, -s at line #215, allows to active security applications such as arpspoof. More applications will be added to the program later on.

  • The core of ARP spoofing detection code is located in the callback function process_packet() when an ARP packet is captured, at lines #110 through #130. The STL set arpRequests is used to store IP addresses of hosts prompted with an ARP request. When an ARP reply is captured, the application makes sure there is a corresponding ARP request in arpRequests. If so, the ARP reply is assumed legitimate. Otherwise, the ARP reply is assumed to be a spoofing attempt and an alert is displayed (line #122).

The ARP spoofing detection code implemented in sniff10.cpp could be further hardened to minimize false positives. The presented implementation favors simplicity and ease of comprehension over robustness.

%root> ./sniff10 -q -s arpspoof
device = eth0
network ip = 172.16.179.0
network mask = 255.255.255.0
arp spoofing detection enabled...

**** ALERT - Potential ARP spoofing detected ****
     unsollicited ARP reply to 00.50.56.ec.28.a3
     originating from 00.0c.29.19.22.3a

^C
*** Capture process interrupted by user...
*** 67 datagrams captured
%root>

As a final note, ARP spoofing is sometimes used for legitimate reasons such as implementing redundancy of network services or debugging IP traffic between specific hosts. Consequently, detected spoofed ARP traffic should be investigated prior to taking countermeasures.


Home  |  Previous  |  Next

 
Copyright © 2014 Marco Lavoie