Network programming in Linux

Extracting Ethernet information


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


Managing data - the Datagram class

In order to analyze captured datagrams, we build a C++ class encapsulating the bytes of a single datagram. The Datagram class is relatively simple, essentially consisting of two attributes:

  1. p_data: array storing the bytes of the captured datagram.
  2. p_len: size of array p_data in bytes.

The main purpose of this class is to hold the captured bytes of a datagram in order to eventually share this data with other class instances to extract TCP/IP information such as Ethernet, IP, TCP and UDP headers. The advantages of this implementation strategy will become apparent in the next sections of the tutorial. First let's look at the Datagram class and how it's used to display raw datagram bytes. The class definition is pretty self-explanatory.

datagram.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
#ifndef DATAGRAM_H
#define DATAGRAM_H

#include <iostream>
#include <pcap.h>         // libpcap (to get bpf_u_int32) 

using namespace std;

/* Datagram: class managing a datagram as an array of bytes.
 *
 * Attributes
 *   p_data : array of bytes
 *   p_len  : size of p_data
 *
 * Notes:
 *   1. memory block referenced by p_data is owned by the instance.
 *   2. memory block p_data is often shared with instances of classes derived from 
 *      DatagramSegment. So when you destroy an instance of Datagram, make sure
 *      no instance of another class shares its p_data block, otherwise you
 *      may get segmentation faults.
 */
class Datagram {
public:
  Datagram();                                    // default constructor
  Datagram(const u_char *, const bpf_u_int32);   // parameterized constructor
  ~Datagram();                                   // destructor
    
  unsigned int length();                         // length of p_data in bytes
    
  // Operator overloading
  Datagram & operator=(const unsigned char *);
  friend ostream & operator<<(ostream &, const Datagram &);

protected:
  unsigned char * p_data;                        // memory block holding datagram bytes
  unsigned int    p_len;                         // length of p_data in bytes
};

#endif

And the member definitions of the Datagram class are:

datagram.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
#ifndef DATAGRAM_CPP
#define DATAGRAM_CPP

#include <cstring>      // copy
#include "datagram.h"

// Default constructor
Datagram::Datagram() {
  p_data = NULL;
  p_len  = 0;
}

// Parameterized constructor
Datagram::Datagram(const u_char *pkt, const bpf_u_int32 l) {
  p_data = new unsigned char[l];
    
  // Copy memory block
  std::copy(pkt, pkt+l, p_data);
  p_len = l;
}

// Destructor
Datagram::~Datagram() {
  delete [] p_data;
}

// Returns number of bytes in datagram.
unsigned int Datagram::length() {
  return p_len;
}    

// Assignment operator accepting a char array as source.
Datagram & Datagram::operator=(const unsigned char * s) {
  if (p_data)
    memcpy(p_data, s, length());
    
  return *this;
}

// Output operator displaying bytes of datagram in hexadecimal and textual forms.
ostream & operator<<(ostream & ostr, const Datagram & pkt) {
  const int LEN = 16;            // number of bytes to display per line
  char      outstr[8],           // for output formatting purposes
            ascii[LEN];          // holds textual bytes for a line
    
  // Display all bytes in hexadecimal and textual forms
  for (unsigned int i = 0; i < pkt.p_len; i++) {
    // Do we need to change line?
    if (i%LEN == 0) {
      // Before moving to the next line, display accumulated bytes in character form
      if (i > 0) {
        ostr << "  ";
        for (int j = 0; j < LEN; j++) ostr << ascii[j];
      }

      // Change line and display position of next byte in p_data
      sprintf(outstr, "%.4d: ", i);
      ostr << endl << outstr;
    }

    // Display byte in hexadecimal
    sprintf(outstr, "%.2x ", (unsigned char)pkt.p_data[i]);
    ostr << outstr;

    // Format byte for textual form 
    ascii[i%LEN] = ((pkt.p_data[i] >= 32 && pkt.p_data[i] <= 126) ? pkt.p_data[i] : '.');
  }

  // Display last line of bytes in textual form
  for (int i = LEN - pkt.p_len % LEN; i > 0; i--) ostr << "   ";
  ostr << "  ";
  for (unsigned int j = 0; j < pkt.p_len % LEN; j++) ostr << ascii[j];

  return ostr;
}

#endif

The only non obvious function in datagram.cpp is the output operator overload, operator<<(), which displays the captured bytes in both hexadecimal and textual forms, sixteen bytes per line.

We can now update the callback function to map the captured bytes into a Datagram instance for display:

sniff04.cpp (partial)
020
021
022
023
024
025
026
027
// Callback given to pcap_loop() for processing captured datagrams
void process_packet(u_char *user, const struct pcap_pkthdr * h, const u_char * packet) {
  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 << flush;
}

We added a new command-line argument (-r) to trigger the raw display of captured datagrams (through the show_raw boolean). Here is the result of capturing a single datagram:

%root> g++ -o sniff04 *.cpp -lpcap
%root> ./sniff04 -p -n 1 -r
device = eth0 (promiscuous)
network ip = 172.16.179.0
network mask = 255.255.255.0
Grabbed 98 bytes (100%) of datagram received on Wed Jul 3 12:01:29 2013
Raw data = 
0000: 00 50 56 ec 28 a3 00 0c 29 19 22 3a 08 00 45 00 .PV.(...).":..E.
0016: 00 54 00 00 40 00 40 01 e3 f3 ac 10 b3 84 ad c2 .T..@.@.........
0032: 49 5e 08 00 69 7d 0e 27 00 07 09 75 d4 51 aa 8a I^..i}.'...u.Q..
0048: 0d 00 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 ................
0064: 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 .......... !"#$%
0080: 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 &'()*+,-./012345
0096: 36 37 67
%root>

Each line of bytes starts with the position of the next byte in the datagram (for example, byte 0d at beginning of the fourth line is the 48th byte in the datagram), followed by sixteen bytes in hexadecimal form and ending with these same bytes in textual form (non displayable characters are replaced by dots).

Our latest sniffer (sniff04.cpp) can now display the content of captured datagrams, but its usefulness is limited since it is quite difficult to extract meaningful information from a sequence of hexadecimal values. The next development step is therefore map datagram bytes into TCP/IP data fields. To accomplish this, we must implement classes representing important protocols of the protocol stack: Ethernet, ARP, IP, ICMP, TCP, UDP and so on... All these new classes require a common base class.

Base class for TCP/IP protocols - the DatagramFragment class

A Datagram instance holds the raw bytes of a captured datagram. If the network device which captured it is Ethernet, these bytes should make up an Ethernet frame, which in turn will encapsulate an upper layer protocol such as an IP packet, and so on. The bytes in a Datagram instance can therefore be mapped to more meaningful class instances which may interpret in these bytes as datagram headers and data.

Since Ethernet frames, IP packets and TCP/UDP segments can be mapped directly onto parts of Datagram instances, we implement a new abstract base class, DatagramFragment, unifying all classes implementing TCP/IP protocols:

DatagramFragment class hierarchy

The DatagramFragment base class resembles Datagram but does not assume ownership of the p_data array holding the datagram bytes. By default, a DatagramFragment's p_data attribute points to a subset of the Datagram instance's p_data array holding the actual captured bytes:

Datagram versus DatagramFragment

In this example, the Datagram instance holds 98 captured bytes (each byte value depicted by two hexadecimal digits) and the DatagramFragment instance points to the first of six consecutive bytes stored in the Datagram instance. Such data storage strategy allows multiple DatagramFragment instances to be mapped to a single Datagram instance's p_data block, preventing duplication of the captured bytes into multiple DatagramFragment instances:

Datagram instance sharing its data

In this example, the Datagram instance holds captured bytes of an Ethernet frame which starts with a destination MAC address followed by a source MAC address. A description of the Ethernet header follows.

Ethernet frame format

A datagram transiting on an Ethernet link is called an Ethernet frame. A frame begins with a preamble and a start frame delimiter. Next comes an Ethernet header containing destination and source MAC addresses. The middle section of the frame is payload data, including headers for other protocols (e.g. Internet Protocol) carried by the frame. The frame ends with a 32-bit cyclic redundancy check used to detect any corruption of data in transit.

The following figure illustrates a basic Ethernet frame format, where each dark blue box represents a single byte while longer boxes represent blocks of more than one byte:

Ethernet frame header 

  • Preamble and Start Frame Delimiter: A frame starts with a 7 bytes preamble and 1byte start frame delimiter (SFD). The bit pattern for this portion of the frame is 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101011.This pattern allows to distinguish one frame from the next on the wire.

  • Destination MAC: A 6 bytes Media Access Control (MAC) address corresponding the Ethernet device to which is destined the frame.

  • Source MAC: A 6 bytes Media Access Control (MAC) address corresponding the Ethernet device which is to receive the frame.

  • Frame Type: This 2 bytes field is used for two different purposes. Values of 1500 (0x05DC) and below indicate that it is used as the size of the payload in bytes while values of 1536 (0x0600) and above indicate that it is used to represent EtherType. The EtherType type is used to indicate which protocol is encapsulated in the frame's payload (for example 0x0806 for ARP, 0x0800 for IPv4, 0x86DD for IPv6 and 0x8100 for 802.1Q).

  • Payload: The minimum payload is 46 bytes and the maximum payload is 1500 bytes. If the amount of user data in the frame is less than 46 bytes the payload is padded with zeros to reach 46 bytes.

  • Frame Check Sequence: The FCS is a 4 bytes cyclic redundancy check which allows detection of corrupted data within the entire frame.

  • Interframe Gap: A gap of idle time between frames. After a frame has been sent, transmitters are required to transmit a minimum of 96 bits (12 bytes) of idle line state before transmitting the next frame.

802.Q1 frame

IEEE 802.1Q is the networking standard that supports Virtual LANs (VLANs) on an Ethernet network. The standard defines a system of VLAN tagging for Ethernet frames and the accompanying procedures to be used by bridges and switches in handling such frames. The standard also contains provisions for a quality of service prioritization scheme.

802.1Q does not actually encapsulate the original frame. Instead, for Ethernet frames, it adds a 4 bytes field between the Source MAC address and the Frame Type fields in the original frame:

802.1Q Etherney frame header

Of the 4 additional bytes added to the original frame, two bytes are used for the Tag Protocol Identifier (TPID), the other two bytes for Tag Control Information (TCI):

802.1Q Header

  • Tag Protocol Identifier (TPID): a 16-bit field set to 0x8100 in order to identify the frame as an IEEE 802.1Q tagged frame. This field is located at the same position as the Frame Type field in untagged frames, and is thus used to distinguish tagged frames from untagged ones.

  • Tag Control Information (TCI):

    • Priority Code Point (PCP): a 3-bit field which refers to the IEEE 802.1Q frame priority level. Values range from 0 (best effort) to 7 (highest), 1 representing the lowest priority.

    • Drop Eligible Indicator (DEI): a 1-bit field that may be used separately or in conjunction with PCP to indicate frames eligible to be dropped in times of congestion.

    • VLAN Identifier (VID): a 12-bit field specifying the VLAN to which the frame belongs. The values 0x000 and 0xFFF are reserved, but all other values may be used as VLAN identifiers, allowing up to 4,094 VLANs.

The 802.1Q Ethernet standard extends the maximum frame size from 1518 bytes to 1522 bytes, leaving the minimum frame size unchanged at 64 bytes.

Note that the 802.1Q Ethernet frame header can be further extended by 4 more bytes, a process called double tagging, to allow Internet service providers to use VLANs internally while mixing traffic from clients that are already VLAN-tagged. This extension to 802.1Q is however beyond the scope of this tutorial.

Implementing the Ethernet frame - the EthernetFrame class

Before getting into implementing the EthernetFrame class, we first must mention that Ethernet frames captured by libpcap are automatically stripped of their leading preamble and SFD fields, as well as their leading FCS and interframe gap fields. Therefore these removed fields are not available to the callback function, its packet parameter pointing to the first byte of the Ethernet frame's destination MAC field.

The EthernetFrame class maps raw captured bytes (stored in a Datagram instance) into corresponding Ethernet frame header fields; it is therefore derived from the DatagramFragment class.

ethernetframe.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
#ifndef ETHERNETFRAME_H
#define ETHERNETFRAME_H

#include <iostream>

#include "datagramfragment.h"   // DatagramFragment
#include "macaddress.h"         // MacAddress

using namespace std;

/* EthernetFrame: class mapping the inherited data block as an Ethernet frame.
 *
 * 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 EthernetFrame : public DatagramFragment {
public:
  // Enumeration of major higher layer protocols which may be transported
  // by the instance 
  typedef enum {
    et_Length, et_DEC, et_XNS, et_IPv4, et_ARP, et_Domain, et_RARP, et_IPX, 
    et_AppleTalk, et_802_1Q, et_IPv6, et_loopback, et_other, et_none
  } EtherType;

  EthernetFrame(bool = false);                           // default constructor
  EthernetFrame(bool, unsigned char *, unsigned int);    // parameterized constructor

  // Returns Mac adresses within the frame header
  MacAddress destination_mac() const;
  MacAddress source_mac() const;

  // Returns ethernet header fields content
  EtherType ether_type() const;
  unsigned int ether_code() const; 

  // Returns 802.1Q fields (if any)
  unsigned int PCP_8021Q() const;
  unsigned int DEI_8021Q() const;
  unsigned int VID_8021Q() const;

  unsigned int header_length() const;         // number of bytes making the datagram's header

  // Operator overloading
  friend ostream & operator<<(ostream &, const EthernetFrame &);
};

#endif

And here are the member definitions for EthernetFrame:

ethernetframe.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
#ifndef ETHERNETFRAME_CPP
#define ETHERNETFRAME_CPP

#include "ethernetframe.h"
#include "exceptions.h"    // EBadTransportException

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

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

// Extracts from Ethernet header what protocol this transports in its data
unsigned int EthernetFrame::ether_code() const {
  if (p_data)
    return char2word(p_data+12);    // code stored in two bytes
  else
    return 0;
}

// Extracts from Ethernet header the priority code point (PCP) when this transports 
// a 802.1Q frame. This code is stored in the 3 most significant bits of the header's 
// 14th byte
unsigned int EthernetFrame::PCP_8021Q() const {
  return p_data[14] >> 5;
}

// Extracts from Ethernet header the drop eligible indicator (DEI) when this transports 
// a 802.1Q frame. This code is stored in the 4th most significant bit of the header's 
// 14th byte
unsigned int EthernetFrame::DEI_8021Q() const {
  return (p_data[14] >> 4) & 0x01;
}

// Extracts from Ethernet header the vlan identifier (VID) when this transports 
// a 802.1Q frame. This code is stored in the 12 least significant bits of the header's 
// 14th and 15th bytes
unsigned int EthernetFrame::VID_8021Q() const {
  return (char2word(p_data+14)) & 0x0FFF;
}

// Returns an enum value corresponding to what this transports. Only the most frequent 
// layer two protocols arew listed - there are more than one hundred of them in reality!
EthernetFrame::EtherType EthernetFrame::ether_type() const {
  if (ether_code() <= 0x05DC)
    return et_Length;
  else
    switch (ether_code()) {
      case 0x6000 : return et_DEC;
      case 0x0609 : return et_DEC;
      case 0x0600 : return et_XNS;
      case 0x0800 : return et_IPv4;
      case 0x0806 : return et_ARP;
      case 0x8019 : return et_Domain;
      case 0x8035 : return et_RARP;
      case 0x8037 : return et_IPX;
      case 0x809B : return et_AppleTalk;
      case 0x8100 : return et_802_1Q; 
      case 0x86DD : return et_IPv6;
      case 0x9000 : return et_loopback;
      default     : return et_other;
  }
}

// Returns the Ethernet header length, which depends on the type of frame
unsigned int EthernetFrame::header_length() const {
  if (!p_data) 
    return 0;
  else if (ether_type() == et_802_1Q)
    return 18;
  else
    return 14;
}    

// Extracts the destination Mac address from the Ethernet header
MacAddress EthernetFrame::destination_mac() const {
  return MacAddress(false, p_data);
}

// Extracts the source Mac address from the Ethernet header
MacAddress EthernetFrame::source_mac() const {
  return MacAddress(false, p_data+6);
}

// Output operator displaying the Ethernet header fields in human readable
// form
ostream & operator<<(ostream & ostr, const EthernetFrame & ether) {
  if (ether.p_data) {
    char outstr[8];
        
    // Display Mac addresses
    ostr << "destination MAC address = " << ether.destination_mac() << endl;
    ostr << "source MAC address = "      << ether.source_mac() << endl;
        
    // Display the hexadecimal value of the Ethernet code field (i.e. what the frame
    // transports)
    sprintf(outstr, "0x%.4x", ether.ether_code());

    // Display the Ethernet code field in textual form. If it's 802.1Q type, the
    // code identifier is dsiplayed later on
    ostr << "ether type = ";
    switch (ether.ether_type()) {
      case EthernetFrame::et_Length    : ostr << "Length field [" << outstr << "]" << endl; break;
      case EthernetFrame::et_DEC       : ostr << "DEC ["          << outstr << "]" << endl; break;
      case EthernetFrame::et_XNS       : ostr << "XNS ["          << outstr << "]" << endl; break;
      case EthernetFrame::et_IPv4      : ostr << "IPv4 ["         << outstr << "]" << endl; break;
      case EthernetFrame::et_ARP       : ostr << "ARP ["          << outstr << "]" << endl; break;
      case EthernetFrame::et_Domain    : ostr << "Domain ["       << outstr << "]" << endl; break;
      case EthernetFrame::et_RARP      : ostr << "RARP ["         << outstr << "]" << endl; break;
      case EthernetFrame::et_IPX       : ostr << "IPX ["          << outstr << "]" << endl; break;
      case EthernetFrame::et_AppleTalk : ostr << "AppleTalk ["    << outstr << "]" << endl; break;
      case EthernetFrame::et_IPv6      : ostr << "IPv6 ["         << outstr << "]" << endl; break;
      case EthernetFrame::et_loopback  : ostr << "loopback ["     << outstr << "]" << endl; break;
      default                          : ostr << "unknown ["      << outstr << "]" << endl; break;
    }
            
    // If the frame is 802.1Q, the header contains 4 more bytes (18 instead of 14).
    // We therefore display the extended fields
    if (ether.ether_type() == EthernetFrame::et_802_1Q) {
      sprintf(outstr, "0x%.4x", char2word(ether.p_data+12));
      ostr << "ether type = 802.1Q [" << outstr << "]" << endl;
            
      ostr << "802.1Q priority code point (PCP) = "     << ether.PCP_8021Q() << endl;
      ostr << "802.1Q drop eligible indicator (DEI) = " << ether.DEI_8021Q() << endl;
      ostr << "802.1Q vlan identifier (VID) = "         << ether.VID_8021Q() << endl;
    }
  }
    
  ostr << flush;
    
  return ostr;
}

#endif

Here are the major keys points of the EthernetFrame implementation:

  • Line #027 in ethernetframe.h defines an enumeration of common payloads (EtherType).

  • Lines #037 and #038 reference a new class, MacAddress, which is used to represent MAC addresses. This class is defined further below.

  • Methods at lines #026, #033 and #040 in ethernetframe.cpp extract 802.1Q field values.

  • Method ether_type() at line #046 converts the frame's payload type constant into the corresponding EtherType enumeration value.

  • Method header_length() at line #068 checks if the frame is 802.1Q, adjusting the returned length accordingly.

  • Methods at lines #078 and #083 extract destination and source MAC addresses, respectively, and convert them into MacAddress instances.

  • Finally, the operator<<() overload at line #089 extracts and displays all Ethernet frame header values.

The MacAddress class, a DatagramFragment descendant for mapping captured bytes into Ethernet MAC addresses, is simple. The one distinguishing functionality is the relational operator overloads (operator==() and operator<()) which will eventually allow us to sort captured Ethernet frames according to MAC addresses.

Here is the MacAddress class:

macaddress.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
#ifndef MACADDRESS_H
#define MACADDRESS_H

#include <iostream>

#include "datagramfragment.h"   // DatagramFragment

using namespace std;

/* MacAddress: class mapping the inherited data block as a MAC address.
 *
 * 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 MacAddress : public DatagramFragment {
public:
  MacAddress(bool = false);                  // default constructor
  MacAddress(bool, unsigned char *);         // parameterized constructor
    
  unsigned int header_length() const;        // length of MAC address in bytes
    
  bool valid() const;                        // indicates if it's a valid device address

  // Operator overloads
  friend ostream & operator<<(ostream &, const MacAddress &);
    
  bool operator==(const MacAddress &) const;
  bool operator<(const MacAddress &) const;
};

#endif

And its implementation:

macaddress.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
#ifndef MACADDRESS_CPP
#define MACADDRESS_CPP

#include "macaddress.h"

#define MAC_LEN 6        // length of MAC address in bytes

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

// Parameterized constructor
MacAddress::MacAddress(bool owned, unsigned char * s) 
  : DatagramFragment(owned, s, MAC_LEN) {}

// Returns length of MAC address (6 bytes)
unsigned int MacAddress::header_length() const {
  return MAC_LEN;
}    

// Returns true if this is a device MAC address (i.e. not 0 of broadcast)
bool MacAddress::valid() const {
  for (unsigned int i = 0; i < this->length(); i++)
    if (this->p_data[i] == 0x00 || this->p_data[i] == 0xFF)
      return false;

  return true;
}

// Output operator displaying the MAC address in dot form (XX.XX.XX.XX.XX.XX)
ostream & operator<<(ostream & ostr, const MacAddress & mac) {
  char outstr[3];
    
  for (unsigned int i = 0; i < mac.length(); i++) {
    sprintf(outstr, "%.2x", mac.p_data[i]); 
    ostr << outstr;
    if (i < mac.length()-1) ostr << '.';
  }
    
  return ostr;
}

// Relational operator comparing MAC addresses at byte level
bool MacAddress::operator==(const MacAddress &mac) const {
  if (this->length() != mac.length())
    return false;
  else {
    for (unsigned int i = 0; i < this->length(); i++)
       if (this->p_data[i] != mac.p_data[i])
          return false;

    return true;
  }
}

// Relational operator comparing MAC addresses at byte level
bool MacAddress::operator<(const MacAddress &mac) const {
  unsigned int len = (mac.length() < this->length() ? mac.length() : this->length());

  for (unsigned int i = 0; i < len; i++)
    if (this->p_data[i] < mac.p_data[i])
      return true;
    else if (this->p_data[i] > mac.p_data[i])
      return false;

  return this->length() < mac.length();
}

#endif

The next improvement to the program is to enhance the Datagram class in order to map captured data into EthernetFrame instances:

datagram.cpp (partial)
048
049
050
051
// Returns an EthernetFrame instance mapped onto the transported data
EthernetFrame Datagram::ethernet() const {
  return EthernetFrame(false, p_data, p_len);
}

Finally, we update the callback function to map the captured bytes into a EtherFrame instance for display:

sniff05.cpp (partial)
048
049
050
051
052
053
054
055
056
057
058
059
060
// Callback given to pcap_loop() for processing captured datagrams
void process_packet(u_char *user, const struct pcap_pkthdr * h, const u_char * packet) {
  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 << flush;

  EthernetFrame ether = pkt.ethernet();   // get EthernetFrame instance from transported data
  cout << "---------- Ethernet frame header ----------" << endl << ether;
  
  cout << endl << flush;
}

Here is the resulting display after capturing Ethernet frames encapsulating IPv4 and ARP traffic:

%root> ./sniff05 
device = eth0
network ip = 172.16.179.0
network mask = 255.255.255.0
Grabbed 156 bytes (100%) of datagram received on Fri Jul 19 16:20:04 2013
---------- Ethernet frame header ----------
destination MAC address = ff.ff.ff.ff.ff.ff
source MAC address = 00.0c.29.19.22.3a
ether type = IPv4 [0x0800]

Grabbed 156 bytes (100%) of datagram received on Fri Jul 19 16:20:04 2013
---------- Ethernet frame header ----------
destination MAC address = ff.ff.ff.ff.ff.ff
source MAC address = 00.0c.29.19.22.3a
ether type = IPv4 [0x0800]

Grabbed 42 bytes (100%) of datagram received on Fri Jul 19 16:20:17 2013
---------- Ethernet frame header ----------
destination MAC address = 00.50.56.c0.00.08
source MAC address = 00.0c.29.19.22.3a
ether type = ARP [0x0806]

Grabbed 60 bytes (100%) of datagram received on Fri Jul 19 16:20:17 2013
---------- Ethernet frame header ----------
destination MAC address = 00.0c.29.19.22.3a
source MAC address = 00.50.56.c0.00.08
ether type = ARP [0x0806]

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

We have built a datagram capturing tool which can analyze Ethernet frame. Theses frames may encapsulate various protocols in their payload (as illustrated by the EtherType enumeration in ethernetframe.h). The next step is to analyze the most common of these protocols travelling throughout Internet: IP version 4 (IPv4).


Home  |  Previous  |  Next

 
Copyright © 2014 Marco Lavoie