Network programming in Linux

Internet Control Message Protocol (ICMP)


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 Internet Control Message Protocol (ICMP) is used by network devices, like routers, to send error messages indicating, for example, that a requested service is not available or that a host or router could not be reached.

ICMP messages are sent in various situations, such as:

  • when a datagram cannot reach its destination;

  • when the gateway does not have the buffering capacity to forward a datagram;

  • when the gateway can direct the host to send traffic on a shorter route;

  • when a datagram is dropped after circulating too long without reaching its destination.

Since the Internet Protocol (IP) does not guarantee delivery, the purpose of ICMP is to provide feedback about problems in the communication environment when IP fails. But since ICMP is transported by IP (i.e. an ICMP datagram is always encapsulated as payload of an IP datagram), ICMP cannot by itself make IP reliable.

ICMP messages typically report errors in the processing of datagrams. To avoid the infinite regress of messages about messages, no ICMP messages are sent about ICMP failures.

Many commonly used network utilities are based on ICMP messages, such as traceroute and ping. These network tools will be implemented later in this tutorial.

The ICMP packet format

All ICMP packets have an 8-byte header and variable-sized data section. The first 4 bytes of the header are consistent: the first byte is the ICMP type, the second byte is the ICMP code, and the third and fourth bytes are a checksum of the entire ICMP message. The content of the remaining 4 bytes vary according to ICMP type and code.

ICMP packet

Here are the most noticeable type/code control messages:

Type Code Description
0 – Echo Reply 0 Echo reply (used to ping)
3 – Destination Unreachable 0 Destination network unreachable
1 Destination host unreachable
2 Destination protocol unreachable
3 Destination port unreachable
4 Fragmentation required, and DF flag set
5 Source route failed
6 Destination network unknown
7 Destination host unknown
8 Source host isolated
9 Network administratively prohibited
10 Host administratively prohibited
11 Network unreachable for TOS
12 Host unreachable for TOS
13 Communication administratively prohibited
14 Host Precedence Violation
15 Precedence cutoff in effect
4 – Source Quench 0 Source quench (congestion control)
5 – Redirect Message 0 Redirect Datagram for the Network
1 Redirect Datagram for the Host
2 Redirect Datagram for the TOS & network
3 Redirect Datagram for the TOS & host
8 – Echo Request 0 Echo request (used to ping)
9 – Router Advertisement 0 Router Advertisement
10 – Router Solicitation 0 Router discovery/selection/solicitation
11 – Time Exceeded 0 TTL expired in transit
1 Fragment reassembly time exceeded
12 – Parameter Problem: Bad IP header 0 Pointer indicates the error
1 Missing a required option
2 Bad length
13 – Timestamp 0 Timestamp
14 – Timestamp Reply 0 Timestamp reply
15 – Information Request 0 Information Request
16 – Information Reply 0 Information Reply
17 – Address Mask Request 0 Address Mask Request
18 – Address Mask Reply 0 Address Mask Reply

ICMP error messages (for example types 3, 5 and 11) contain a data section that includes the entire IP header plus the first 8 bytes of data from the IP packet that caused the error message. ICMP packets are encapsulated in IP packets.

For more information on ICMP, and especially on the various payload contents, see the official reference document RFC 792.

The ICMPPacket class

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

icmppacket.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
#ifndef ICMPPACKET_H
#define ICMPPACKET_H

#include <iostream>

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

using namespace std;

/* ICMPPacket: class mapping the inherited data block as an ICMP 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 ICMPPacket : public DatagramFragment {
  public:
    ICMPPacket(bool = false);                             // constructor
    ICMPPacket(bool, unsigned char *, unsigned int);      // parameterized constructor
    
    unsigned int header_length() const;                   // length of ARP packet header in bytes

    const char * description() const;                     // returns a textual description of ICMP packet

    // Routines returning common header field values
    unsigned int type() const;
    unsigned int code() const;
    unsigned int checksum() const;
    
    // Routines returning header field values depending on packet type
    unsigned int identifier() const;
    unsigned int sequence_number() const;
    
    unsigned int originate_timestamp() const;
    unsigned int receive_timestamp() const;
    unsigned int transmit_timestamp() const;
    
    unsigned int next_hop_MTU() const;

    IPAddress    ipaddress() const;
    IPAddress    address_mask() const;
    
    // Operator overloads
    friend ostream & operator<<(ostream &, const ICMPPacket &);
    
  protected:
};

#endif

Here are the member definitions of the ICMPPacket class.

icmppacket.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
195
196
197
198
199
200
201
202
203
#ifndef ICMPPACKET_CPP
#define ICMPPACKET_CPP

#include "icmppacket.h"  // ICMPPacket
#include "exceptions.h"  // EBadTransportException

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

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

// Returns the ICMP header length in bytes
unsigned int ICMPPacket::header_length() const {
  if (!p_data) 
    return 0;
  else
    return 8;
}

// Returns a textual description of the packet according to its type and code fields
const char * ICMPPacket::description() const {
  unsigned int msg_id = type() << 8 + code();

  switch (msg_id) {
    case 0x0000 : return "echo reply";
    case 0x0300 : return "network unreachable";
    case 0x0301 : return "host unreachable";
    case 0x0302 : return "protocol unreachable";
    case 0x0303 : return "port unreachable";
    case 0x0304 : return "fragmentation needed and Don't Fragment flag set";
    case 0x0305 : return "source route failed";
    case 0x0306 : return "destination network unknown";
    case 0x0307 : return "destination host unknown";
    case 0x0308 : return "source host isolated";
    case 0x0309 : return "communication with destination network is administratively prohibited";
    case 0x030A : return "communication with destination host is administratively prohibited";
    case 0x030B : return "destination network unreachable for type of service";
    case 0x030C : return "destination host unreachable for type of service";
    case 0x030D : return "communication administratively prohibited ";
    case 0x030E : return "host precedence violation";
    case 0x030F : return "precedence cutoff in effect";
    case 0x0400 : return "source quench";
    case 0x0500 : return "redirect datagram for the network (or subnet)";
    case 0x0501 : return "redirect datagram for the host";
    case 0x0502 : return "redirect datagram for the type of service and network";
    case 0x0503 : return "redirect datagram for the type of service and host";
    case 0x0600 : return "alternate address for host";
    case 0x0800 : return "echo request";
    case 0x0900 : return "normal router advertisement";
    case 0x0910 : return "does not route common traffic";
    case 0x0A00 : return "router selection";
    case 0x0B00 : return "time to live exceeded in transit";
    case 0x0B01 : return "fragment reassembly time exceeded";
    case 0x0C00 : return "pointer indicates the error";
    case 0x0C01 : return "missing a required option";
    case 0x0C02 : return "bad length";
    case 0x0D00 : return "timestamp";
    case 0x0E00 : return "timestamp reply";
    case 0x0F00 : return "information request";
    case 0x1000 : return "information reply";
    case 0x1100 : return "address mask request";
    case 0x1200 : return "address mask reply";
    case 0x1300 : return "reserved (for security)";
    case 0x1E00 : return "traceroute";
    case 0x1F00 : return "datagram conversion error";
    default     : return "unknown ICMP packet";
  }
}

// Returns content of type header field
unsigned int ICMPPacket::type() const {
  return p_data[0];
}

// Returns content of code header field
unsigned int ICMPPacket::code() const {
  return p_data[1];
}    

// Returns content of checksum header field
unsigned int ICMPPacket::checksum() const {
  return char2word(p_data+2);
}

// Returns content of identifier header field for type 13, 14, 17 or 18 ICMP packets
unsigned int ICMPPacket::identifier() const {
  if (code() == 0 || (type() == 13 || type() == 14 || type() == 17 || type() == 18))
    return char2word(p_data+4);
  else
    throw EBadTransportException("ICMP packet does not hold identifier field");
}

// Returns content of sequence number header field for type 13, 14, 17 or 18 ICMP packets
unsigned int ICMPPacket::sequence_number() const {
  if (code() == 0 || (type() == 13 || type() == 14 || type() == 17 || type() == 18))
    return char2word(p_data+6);
  else
    throw EBadTransportException("ICMP packet does not hold sequence number field");
}

// Returns content of next-hop MTU header field for type 3 ICMP packets
unsigned int ICMPPacket::next_hop_MTU() const {
  if (type() == 3)
    return char2word(p_data+6);
  else
    throw EBadTransportException("ICMP packet does not hold next-hop MTU field");
}

// Returns content of originate timestamp header field for type 13 or 14 ICMP packets
unsigned int ICMPPacket::originate_timestamp() const {
  if (code() == 0 && (type() == 13 || type() == 14))
    return char4word(p_data+8);
  else
    throw EBadTransportException("ICMP packet does not hold originate timestamp field");
}

// Returns content of receive timestamp header field for type 14 ICMP packets
unsigned int ICMPPacket::receive_timestamp() const {
  if (code() == 0 && type() == 14)
    return char4word(p_data+12);
  else
    throw EBadTransportException("ICMP packet does not hold receive timestamp field");
}

// Returns content of transmit timestamp header field for type 14 ICMP packets
unsigned int ICMPPacket::transmit_timestamp() const {
  if (code() == 0 && type() == 14)
    return char4word(p_data+16);
  else
    throw EBadTransportException("ICMP packet does not hold transmit timestamp field");
}

// Returns content of IP address header field for type 5 ICMP packets
IPAddress ICMPPacket::ipaddress() const {
  if (type() == 5)
    return IPAddress(false, p_data + 4);
  else
    throw EBadTransportException("ICMP packet does not hold IP address field");
}

// Returns content of address mask header field for type 17 or 18 ICMP packets
IPAddress ICMPPacket::address_mask() const {
  if (code() == 0 && (type() == 17 || type() == 18))
    return IPAddress(false, p_data + 8);
  else
    throw EBadTransportException("ICMP packet does not hold address mask field");
}

// Output operator displaying the ICMP packet header fields in human readable
// form
ostream & operator<<(ostream & ostr, const ICMPPacket & icmp) {
  if (icmp.p_data) {
    char outstr[8];

    // Display common header fields
    ostr << "type/code = " << icmp.type() << "/" << icmp.code() << " ("
         << icmp.description() << ")" << endl;

    sprintf(outstr, "0x%.4x", icmp.checksum());
    ostr << "checksum = " << outstr << endl;

    // Display specialized header fields which depend on type and code values
    // We catch any thrown exceptions
    
    // Display identifier and sequence number fields for packets of type 13, 
    // 14, 17 or 18
    try {
      unsigned int identif = icmp.identifier();  
      unsigned int seqnum  = icmp.sequence_number();
      
      sprintf(outstr, "0x%.4x", identif);
      ostr << "identifier = " << outstr << endl;
      ostr << "sequence number = " << seqnum << endl;
    }
    catch (EBadTransportException) {
    }

    // Display next-hop MTU field for packets of type 3
    try {
      unsigned int nexthop = icmp.next_hop_MTU();  
      ostr << "sequence number = " << nexthop << endl;
    }
    catch (EBadTransportException) {
    }

    // Display IP address field for packets of type 5
    try {
      IPAddress addr = icmp.ipaddress();  
      ostr << "IP address = " << addr << endl;
    }
    catch (EBadTransportException) {
    }
  }
    
  ostr << flush;
    
  return ostr;
}

#endif

Here are major key points on the above source code:

  • The method description() returns a textual description of the ICMP packet according to its type and code values.

  • Several methods, such as next_hop_MTU() and originate_timestamp(), give a meaningful interpretation of the last 8 bytes of ICMP header when type and code values are appropriate.

  • Other methods, such as receive_timestamp() and address_mask(), also interpret payload content according to some specific type and code values.

Since ICMP is transported by IP, we add a new method to the IPPacket class for mapping its payload into an ICMPPacket instance:

ippacket.cpp (partial)
183
184
185
186
187
189
190
// Returns ICMP packet transported in payload
ICMPPacket IPPacket::icmp() {
    if (protocol() != ipp_icmp)
        throw EBadTransportException("IP packet not transporting ICMP traffic");

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

Finally, we update the callback function to map the captured bytes of the IPPacket's payload into a ICMPPacket instance for display:

sniff11.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
// 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;
  ICMPPacket icmp;
  
  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;

      // If it's an ICMP packet, displat its attributes
      if (ip.protocol() == IPPacket::ipp_icmp) {
        icmp = ip.icmp();
        COUT << "------ ICMP packet header ------" << endl << icmp;
      }
          
      break;

Here is the resulting display after capturing an ICMP Echo Request packet followed by the resulting ICMP Echo Reply packet. These two ICMP packet types don't use the last four bytes of the header.

%root> ./sniff11 -f icmp -n 2
device = eth0
network ip = 172.16.179.0
network mask = 255.255.255.0
BPF filter = icmp
Grabbed 98 bytes (100%) of datagram received on Sun Sep 22 15:09:46 2013
---------- Ethernet frame header ----------
destination MAC address = 00.50.56.ec.28.a3
source MAC address = 00.0c.29.19.22.3a
ether type = IPv4 [0x0800]
-------- IP packet header --------
version = IPv4
header length = 20 (IHL = 5)
type of service = 0:
total length = 84
fragment ID = 0x0000
  don't fragment = 2
  more fragments = 0
  fragment position = 0
protocol = ICMP [0x01]
time to live = 64
checksum = 0xac3a
destination IP address = 74.125.228.87
source IP address = 172.16.179.137
------ ICMP packet header ------
type/code = 8/0 (echo request)
checksum = 0x486f

Grabbed 98 bytes (100%) of datagram received on Sun Sep 22 15:09:46 2013
---------- Ethernet frame header ----------
destination MAC address = 00.0c.29.19.22.3a
source MAC address = 00.50.56.ec.28.a3
ether type = IPv4 [0x0800]
-------- IP packet header --------
version = IPv4
header length = 20 (IHL = 5)
type of service = 0:
total length = 84
fragment ID = 0x1249
  don't fragment = 0
  more fragments = 0
  fragment position = 0
protocol = ICMP [0x01]
time to live = 128
checksum = 0x99f1
destination IP address = 172.16.179.137
source IP address = 74.125.228.87
------ ICMP packet header ------
type/code = 0/0 (echo reply)
checksum = 0x506f

*** 2 datagrams captured
%root>

Application - Detecting ping floods

A ping flood is a simple denial-of-service attack where the attacker overwhelms the victim host with ICMP Echo Request (ping) packets. This is most effective when using the flood option of ping which sends voluminous ICMP packets as fast as possible without waiting for replies (most implementations of ping require the user to be privileged in order to use flood option). The attacker hopes the victim host responds with ICMP Echo Reply packets, thus consuming both outgoing bandwidth as well as incoming bandwidth. If the target host is legacy (i.e. computationally slow), it may dedicate enough of its CPU cycles to replies for users to notice significant slowdowns.

For implementation considerations, we arbitrarily defines what constitutes a ping flood. These specifications do not "officially" defines a ping flood. A sequence of ICMP Echo Request packets is considered a ping flood if

  1. it targets the same host with at least 100 ICMP Echo Request packets,
  2. the average delay between packets is less than 200 milliseconds, and
  3. the packets carry a payload of at least 1 Kilobytes.

With the definition in mind, we define a new class, PingFloodDetection, which helps detect ping flood attacks to any target host on the local network. The class definition uses a STL map to associate ICMP Echo Requests tracking data to target IP addresses. The tracking data for a single IP address is stored in instances of an embedded class, PingRequestData.

pingflood.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
#ifndef PINGFLOOD_H
#define PINGFLOOD_H

#include <sstream>             // ostringstream
#include <sys/time.h>          // gettimeofday()
#include <map>                 // STL map

#include "ipaddress.h"         // IPAddress
#include "icmppacket.h"        // ICMPPacket

using namespace std;

/* PingFloodDetection: class applying ping flood detection to provided ICMP echo 
 *                     request packets.
 *
 * Attributes
 *   pingFloods : STL map structure for associating ping echo requests data to
                  targeted IP addresses
 */
class PingFloodDetection {
  /* PingRequestData: class storing ICMP Echo Request data for one a single 
   *                  targeted IP address.
   *
   * Attributes
   *   cout_ip: counts the number of successive ICMP ping requests received.
   *   sum_delays: sum of the delays (in milliseconds) between successive ICMP ping 
   *               requests received.
   *   time_last_arrival: time (in milliseconds) elapsed since the last ICMP echo
   *                      request was received.
   */
  struct PingRequestData {
    // Parameterized default constructor
    PingRequestData(unsigned int c = 0, unsigned long s = 0, unsigned long t = 0) 
      : count_ip(c), sum_delays(s), time_last_arrival(t) {
    }
    
    unsigned int  count_ip;             // count of received pings
    unsigned long sum_delays;           // sum of delays between successive pings
    unsigned long time_last_arrival;    // time of arrival of latest ping
  };
  
  public:
    bool process_ping(const IPAddress &, const ICMPPacket &);  // core routine for flood detection

  private:
    map<string, PingRequestData> pingFloods;  // dictionary storing ping data for each targeted IP
};
  
  
#endif

The class PingRequestData is nested within PingFloodDetection for scoping purposes (only PingFloodDetection methods need access to this class).

pingflood.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
#ifndef PINGFLOOD_CPP
#define PINGFLOOD_CPP

#include "pingflood.h"  // PingFloodDetection

// Process ping requests, returning true if a ping flood is detected for the given target
bool PingFloodDetection::process_ping(const IPAddress & target, const ICMPPacket & icmp) {
  const int tol_delay = 200;      // if delay larger than this (in ms), it's not part of an attack
  const int tol_count = 100;      // minimum number of successive pings for an attack to be considered
  const int tol_size  = 1024;     // minimum payload size (in bytes) for an attack to be considered

  // Make sure it's a ping request
  if (icmp.type() != 8 || icmp.code() != 0)
    return false;
    
  // Is the ping request large enough to be considered as part of an attack?
  if (icmp.length() - icmp.header_length() < tol_size)
    return false;
  
  // Get current time (in milliseconds since January 1, 1970) as time of packet arrival
  timeval t;
  gettimeofday(&t, 0);
  unsigned long time_arrival = t.tv_sec * 1000.0 + t.tv_usec / 1000.0;  

  // Convert target IP address to string to serve as key in map
  ostringstream ostr;
  ostr << target;
  string key = ostr.str();
  
  // Is it the first ping for this target?
  map<string, PingRequestData>::iterator it = pingFloods.find(key);
  if (it == pingFloods.end()) {
    // It's a new target so initialize ping data for it
    PingFloodDetection::PingRequestData data(0, 0, time_arrival);
    
    // Add new data structure to map
    pingFloods[key] = data;
    it = pingFloods.find(key);
  }

  // Update ping data for this target
  it->second.count_ip           = it->second.count_ip + 1;
  it->second.sum_delays        += time_arrival - it->second.time_last_arrival;
  it->second.time_last_arrival  = time_arrival;

  // Compute the mean packet interarrival time for this target
  double mean_time = 1.0 * it->second.sum_delays / it->second.count_ip;

  // Is it an attack? If so, delete data for target (to prevent considering the next 
  // ping request as a new attack) and return true to flag the attack
  bool attack = (it->second.count_ip >= tol_count) && (mean_time < tol_delay);
  if (attack) pingFloods.erase(key);

  return attack;
}

#endif

Most data collected for detection purposes is done in the process_ping() method. After validating the ICMP packet as an ICMP Echo Request (line #013) of excessive size (line #017), it checks if the ping is part of an ongoing attack or the first packet of a new attack (lines #031 and #032). It then updates its statistics on the attack (lines #042 through #044) and determine if the results constitute a ping flood attack as arbitrarily defined previously. As soon as an attack is detected, the targeted IP is removed from the map (line #052) so the next ICMP Echo Request to this target does not immediately trigger another alarm.

Finally, modifications are made to the main() routine and its associated code to add ping flood detection as a new application.

sniff12.cpp (partial)



031
032
033
034
035
036
037



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



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
195
196
197
198
199

...

#include "datagram.h"          // Datagram
#include "ethernetframe.h"     // EthernetFrame
#include "ippacket.h"          // IPPacket
#include "arppacket.h"         // ARPPacket
#include "icmppacket.h"        // ICMPPacket

#include "pingflood.h"         // PingFloodDetection

...

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
#define PINGFLOOD 2

// 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;
  static PingFloodDetection pingFloods;

  IPPacket ip;
  ARPPacket arp;
  ICMPPacket icmp;
    
  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;

      // If it's an ICMP packet, displat its attributes
      if (ip.protocol() == IPPacket::ipp_icmp) {
        icmp = ip.icmp();
        COUT << "------ ICMP packet header ------" << endl << icmp;
        
        // Apply ping flood detection if required
        if (security_tool == PINGFLOOD && pingFloods.process_ping(ip.destination_ip(), icmp))
          cout << "**** ALERT - Potential Ping flood detected ****" << endl
               << "     numerous echo requests with large payload targeting" << endl
               << "     host " << ip.destination_ip() << endl << endl;
      }
          
      break;

      ...
      
}

// Sniffer's main program: add ICMP ping flood detection
int main(int argc, char *argv[]) {
  char *device = NULL;            // device to sniff
  char  argch;                    // to manage command line arguments
  char  errbuf[PCAP_ERRBUF_SIZE]; // to handle libpcap error messages
  int   siz     = 1518,           // max number of bytes captured for each datagram
        promisc = 0,              // deactive promiscuous mode
        cnt     = -1;             // capture indefinitely
  char *wlogfname = NULL,         // filename where to log captured datagrams
       *rlogfname = NULL;         // filename from which to read logged datagrams

  // Install Ctrl+C handler
  struct sigaction sa, osa;
  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = &bypass_sigint;
  sigaction(SIGINT, &sa, &osa);

  // Process command line arguments
  while ((argch = getopt(argc, argv, "hpqrd:f:i:l:n:s:")) != EOF)
    switch (argch) {

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

  ...

Here is a sample run of the application when local host 172.16.179.1 is attacked (the Linux command to flood this host with 2 Kb ICMP Echo Request packets would be ping -f -s 2000 172.16.179.1):

%root> ./sniff12 -q -s pingflood
device = eth0
network ip = 172.16.179.0
network mask = 255.255.255.0
ping flood detection enabled...
**** ALERT - Potential Ping flood detected ****
     numerous echo requests with large payload targeting
     host 172.16.179.1

**** ALERT - Potential Ping flood detected ****
     numerous echo requests with large payload targeting
     host 172.16.179.1

**** ALERT - Potential Ping flood detected ****
     numerous echo requests with large payload targeting
     host 172.16.179.1

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

As a final note, ping floods are often used as a diagnostic for network performance analysis and debugging. Consequently, suspicious traffic should always be investigated prior to taking countermeasures.


Home  |  Previous  |  Next

 
Copyright © 2014 Marco Lavoie