Network programming in Linux

User Datagram Protocol (UDP)


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 User Datagram Protocol (UDP) is one of the core members of the Internet protocol suite.

UDP uses a simple transmission model with a minimum of protocol mechanism. It has no handshaking dialogues, and thus exposes any unreliability of the underlying network protocol to user programs. As this is normally IP over unreliable media, there is no guarantee of delivery, ordering or duplicate protection. UDP provides checksums for data integrity, and port numbers for addressing different functions at the source and destination of datagrams.

UDP is suitable for purposes where error checking and correction is either not necessary or performed at the application level, avoiding the overhead of processing these tasks at the network interface level. Time-sensitive applications, such as voice and video streaming, often use UDP since dropping packets is preferable to waiting for delayed packets.

If error correction facilities are needed at the network interface level, an application should use the Transmission Control Protocol (TCP) which is designed for this purpose.

The UDP segment format

UDP accepts data from the TCP/IP application layer, divides it into chunks, and adds an UDP header to each chunk, creating a sequence of UDP segments. Each UDP segment is then encapsulated into an IP packet and transmitted over the Internet.

The UDP header contains 4 fields:

UDP  segment

Here is a brief description of the UDP header fields:

  • Source port: Identifies the sending port number.

  • Destination port: Identifies the receiving port number.

  • Length: Gives the length of the entire UDP segment, including header and payload.

  • Checksum: The 16-bit checksum field is used for error-checking the header and data.

The UDPSegment class

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

udpsegment.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
#ifndef UDPSEGMENT_H
#define UDPSEGMENT_H

#include <iostream>

#include "datagramfragment.h"   // DatagramFragment

using namespace std;

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

    unsigned int source_port() const;                     // source port
    unsigned int destination_port() const;                // destination port
    
    unsigned int len() const;                             // access to length field
    unsigned int checksum() const;                        // access to checksum field

    // Operator overloads
    friend ostream & operator<<(ostream &, const UDPSegment &);
    
  protected:
    
    // Returns a string textually identifying standard ports
    const char * port_name(unsigned int) const;
};

#endif

And here are the member definitions for UDPSegment. Their implementation is straightforward:

udpsegment.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
#ifndef UDPSEGMENT_CPP
#define UDPSEGMENT_CPP

#include "udpsegment.h"

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

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

// Returns the UDP segment header length in bytes
unsigned int UDPSegment::header_length() const {
  return 8;
}

// Returns source port
unsigned int UDPSegment::source_port() const {
  return char2word(p_data);
}

// Returns destination port
unsigned int UDPSegment::destination_port() const {
  return char2word(p_data+2);
}

// Returns the length field    
unsigned int UDPSegment::len() const {
  return char2word(p_data+4);
}

// Returns the checksum field    
unsigned int UDPSegment::checksum() const {
  return char2word(p_data+6);
}

// Returns a string textually identifying most popular standard ports
const char * UDPSegment::port_name(unsigned int num) const {
  switch (num) {
    case  20: 
    case  21: return "FTP";
    case  22: return "SSH";
    case  23: return "telnet";
    case  25: return "SMTP";
    case  53: return "DNS";
    case  67: 
    case  68: return "DHCP";
    case  69: return "TFTP";
    case  80: return "HTTP";
    case 110: return "POP3";
    case 137: 
    case 150: return "NetBIOS";
    case 389: return "LDAP";
    case 546:
    case 547: return "DHCP";
  }
    
  // Distinguish assigned ports from ephemerals
  if (num < 1024)
    return "unknown";
  else  
    return "ephemeral";
}

// Returns a string textually identifying some common standard ports
ostream & operator<<(ostream & ostr, const UDPSegment & udp) {
  if (udp.p_data) {
    char outstr[8];

    ostr << "source port = " << udp.source_port();
    ostr << " [" << udp.port_name(udp.source_port()) << "]" << endl;
        
    ostr << "destination port = " << udp.destination_port();
    ostr << " [" << udp.port_name(udp.destination_port()) << "]" << endl;

    ostr << "length = " << udp.len()  << endl;

    sprintf(outstr, "0x%.4x", udp.checksum());
    ostr << "checksum = " << outstr << endl;
  }
    
  ostr << flush;
    
  return ostr;
}

#endif

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

ippacket.cpp (partial)
199
200
201
202
203
204
205
// Returns UDP segment transported in payload
UDPSegment IPPacket::udp() {
    if (protocol() != ipp_udp)
        throw EBadTransportException("IP packet not transporting UDP traffic");

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

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

sniff15.cpp (partial)
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
100
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
// 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;  
  static map<string, TCPSession> tcpSessions;

  IPPacket   ip;
  ARPPacket  arp;
  ICMPPacket icmp;
  TCPSegment tcp;
  UDPSegment udp;
    
  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 transports an ICMP packet, display 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;
      }

      // If it transports a TCP segment, display its attributes
      else if (ip.protocol() == IPPacket::ipp_tcp) {
        tcp = ip.tcp();
        COUT << "------ TCP segment header ------" << endl << tcp;

        // Apply TCP session tracking if required
        if (security_tool == TCPTRACK) {
          // Compute keys to uniquely identify the session
          string src, dst;
          TCPSession::getKeys(ip, src, dst);
          
          // Is the segment part of a tracked session or not? We need to search 
          // for two keys since segments within a TCP session travel in both
          // directions
          map<string, TCPSession>::iterator it = tcpSessions.find(src + dst);
          if (it == tcpSessions.end()) it = tcpSessions.find(dst + src);
          
          // If it's a new session then start tracking it
          if (it == tcpSessions.end() && (tcp.flag_syn() && !tcp.flag_ack())) {
            tcpSessions[src + dst] = TCPSession(ip);
            it = tcpSessions.find(src + dst);
          }
          
          if (it != tcpSessions.end())
            // Now we track the session to which is associated the TCP segment
            if (it->second.getState() != it->second.trackState(ip, false))
              // If the session has just been closed, display total number of bytes 
              // exchanged since we started tracking it, and destroy it
              if (it->second.terminated()) {
                cout << "Total data exchanged between " << src << " and " << dst << " = " 
                     << it->second.getBytes() << " bytes" << endl;
                     
                // Destroy TCP session tracker
                tcpSessions.erase(it);
              }
        }
      }
          
      // If it transports a UDP segment, display its attributes
      else if (ip.protocol() == IPPacket::ipp_udp) {
        udp = ip.udp();
        COUT << "------ UDP segment header ------" << endl << udp;
      }
      
      break;

Here is the result of capturing UDP segments resulting from pinging a target host outside the local network. These segments correspond to a DNS (Domain Name System) request to the local gateway and the returned answer:

%root> ./sniff15 -f udp -n 2
device = eth0
network ip = 172.16.179.0
network mask = 255.255.255.0
BPF filter = udp
Grabbed 73 bytes (100%) of datagram received on Sun Sep 29 11:41:17 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 = 59
fragment ID = 0x0000
  don't fragment = 2
  more fragments = 0
  fragment position = 0
protocol = UDP [0x11]
time to live = 64
checksum = 0x7c05
destination IP address = 172.16.179.2
source IP address = 172.16.179.137
------ UDP segment header ------
source port = 11164 [ephemeral]
destination port = 53 [DNS]
length = 39
checksum = 0xce3d

Grabbed 116 bytes (100%) of datagram received on Sun Sep 29 11:41:17 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 = 102
fragment ID = 0xa5f0
  don't fragment = 0
  more fragments = 0
  fragment position = 0
protocol = UDP [0x11]
time to live = 128
checksum = 0xd5e9
destination IP address = 172.16.179.137
source IP address = 172.16.179.2
------ UDP segment header ------
source port = 53 [DNS]
destination port = 11164 [ephemeral]
length = 82
checksum = 0xa2d0

*** 2 datagrams captured
%root>

This concludes the transport layer protocols analyzed by our sniffer. The next section implements classes for analyzing an application layer protocol, the Trivial File Transfer Protocol (TFTP).


Home  |  Previous  |  Next

 
Copyright © 2014 Marco Lavoie