1 /*
2  * pcapfile.cxx
3  *
4  * Ethernet capture (PCAP) file declaration
5  *
6  * Portable Tools Library
7  *
8  * Copyright (C) 2011 Vox Lucida Pty. Ltd.
9  *
10  * The contents of this file are subject to the Mozilla Public License
11  * Version 1.0 (the "License"); you may not use this file except in
12  * compliance with the License. You may obtain a copy of the License at
13  * http://www.mozilla.org/MPL/
14  *
15  * Software distributed under the License is distributed on an "AS IS"
16  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17  * the License for the specific language governing rights and limitations
18  * under the License.
19  *
20  * The Original Code is Portable Tools Library.
21  *
22  * The Initial Developer of the Original Code is Vox Lucida
23  *
24  * All Rights Reserved.
25  *
26  * Contributor(s): ______________________________________.
27  *
28  * $Revision: 28601 $
29  * $Author: rjongbloed $
30  * $Date: 2012-11-26 18:45:27 -0600 (Mon, 26 Nov 2012) $
31  */
32 
33 #include <ptlib.h>
34 
35 #ifdef __GNUC__
36 #pragma implementation "pcapfile.h"
37 #endif
38 
39 #include <rtp/pcapfile.h>
40 
41 
42 
Get(const PBYTEArray & p,PINDEX off)43 template <typename T> const T & Get(const PBYTEArray & p, PINDEX off)
44 {
45   return *(const T *)(((const BYTE *)p)+off);
46 }
47 
48 
Reverse(char * ptr,size_t sz)49 void Reverse(char * ptr, size_t sz)
50 {
51   char * top = ptr+sz-1;
52   while (ptr < top) {
53     char t = *ptr;
54     *ptr = *top;
55     *top = t;
56     ptr++;
57     top--;
58   }
59 }
60 
61 #define REVERSE(p) Reverse((char *)&p, sizeof(p))
62 
63 
64 ///////////////////////////////////////////////////////////////////////////////
65 
OpalPCAPFile()66 OpalPCAPFile::OpalPCAPFile()
67   : m_otherEndian(false)
68   , m_filterSrcIP(PIPSocket::GetDefaultIpAny())
69   , m_filterDstIP(PIPSocket::GetDefaultIpAny())
70   , m_fragmentated(false)
71   , m_fragmentProto(0)
72   , m_filterSrcPort(0)
73   , m_filterDstPort(0)
74   , m_packetSrcPort(0)
75   , m_packetDstPort(0)
76 {
77   OpalMediaFormatList list = OpalMediaFormat::GetAllRegisteredMediaFormats();
78   for (PINDEX i = 0; i < list.GetSize(); i++) {
79     if (list[i].GetPayloadType() < RTP_DataFrame::DynamicBase)
80       m_payloadType2mediaFormat[list[i].GetPayloadType()] = list[i];
81   }
82 }
83 
84 
Open(const PFilePath & filename)85 bool OpalPCAPFile::Open(const PFilePath & filename)
86 {
87   if (!PFile::Open(filename, PFile::ReadOnly))
88     return false;
89 
90   if (!Read(&m_fileHeader, sizeof(m_fileHeader))) {
91     PTRACE(1, "PCAPFile\tCould not read header from \"" << filename << '"');
92     return false;
93   }
94 
95   if (m_fileHeader.magic_number == 0xa1b2c3d4)
96     m_otherEndian = false;
97   else if (m_fileHeader.magic_number == 0xd4c3b2a1)
98     m_otherEndian = true;
99   else {
100     PTRACE(1, "PCAPFile\tFile \"" << filename << "\" is not a PCAP file, bad magic number.");
101     return false;
102   }
103 
104   if (m_otherEndian) {
105     REVERSE(m_fileHeader.version_major);
106     REVERSE(m_fileHeader.version_minor);
107     REVERSE(m_fileHeader.thiszone);
108     REVERSE(m_fileHeader.sigfigs);
109     REVERSE(m_fileHeader.snaplen);
110     REVERSE(m_fileHeader.network);
111   }
112 
113   if (GetNetworkLayerHeaderSize() == 0) {
114     PTRACE(1, "PCAPFile\tUnsupported Data Link Layer " << m_fileHeader.network << " in file \"" << filename << '"');
115     return false;
116   }
117 
118   return true;
119 }
120 
121 
Restart()122 bool OpalPCAPFile::Restart()
123 {
124   if (SetPosition(sizeof(m_fileHeader)))
125     return true;
126 
127   PTRACE(2, "PCAPFile\tCould not seek beginning of \"" << GetFilePath() << '"');
128   return false;
129 }
130 
131 
PrintOn(ostream & strm) const132 void OpalPCAPFile::PrintOn(ostream & strm) const
133 {
134   strm << "PCAP v" << m_fileHeader.version_major << '.' << m_fileHeader.version_minor
135                    << " file \"" << GetFilePath() << '"';
136 }
137 
138 
ReadRawPacket(PBYTEArray & payload)139 bool OpalPCAPFile::ReadRawPacket(PBYTEArray & payload)
140 {
141   if (m_fragmentated) {
142     m_fragments.SetSize(0);
143     m_fragmentated = false;
144   }
145 
146   RecordHeader recordHeader;
147   if (!Read(&recordHeader, sizeof(recordHeader))) {
148     PTRACE(1, "PCAPFile\tTruncated file \"" << GetFilePath() << '"');
149     return false;
150   }
151 
152   if (m_otherEndian) {
153     REVERSE(recordHeader.ts_sec);
154     REVERSE(recordHeader.ts_usec);
155     REVERSE(recordHeader.incl_len);
156     REVERSE(recordHeader.orig_len);
157   }
158 
159   m_packetTime.SetTimestamp(recordHeader.ts_sec, recordHeader.ts_usec);
160 
161   if (!Read(m_rawPacket.GetPointer(recordHeader.incl_len), recordHeader.incl_len)) {
162     PTRACE(1, "PCAPFile\tTruncated file \"" << GetFilePath() << '"');
163     return false;
164   }
165 
166   payload.Attach(m_rawPacket, recordHeader.incl_len);
167   return true;
168 }
169 
170 
GetNetworkLayerHeaderSize()171 PINDEX OpalPCAPFile::GetNetworkLayerHeaderSize()
172 {
173   switch (m_fileHeader.network) {
174     case 1 : // DLT_EN10MB - Ethernet (10Mb)
175       return 14;
176 
177     case 113 : // DLT_LINUX_SLL - Linux cooked sockets
178       return 16;
179   }
180 
181   return 0;
182 }
183 
184 
GetDataLink(PBYTEArray & payload)185 int OpalPCAPFile::GetDataLink(PBYTEArray & payload)
186 {
187   PBYTEArray dataLink;
188   if (!ReadRawPacket(dataLink))
189     return -1;
190 
191   PINDEX headerLength = GetNetworkLayerHeaderSize();
192   payload.Attach(&dataLink[headerLength], dataLink.GetSize()-headerLength);
193   return Get<PUInt16b>(dataLink, headerLength-2); // Next protocol layer
194 }
195 
196 
GetIP(PBYTEArray & payload)197 int OpalPCAPFile::GetIP(PBYTEArray & payload)
198 {
199   PBYTEArray ip;
200   if (GetDataLink(ip) != 0x800) // IPv4
201     return -1;
202 
203   PINDEX totalLength = (ip[2]<<8)|ip[3]; // Total length of packet
204   if (totalLength > ip.GetSize()) {
205     PTRACE(2, "Truncated IP packet, expected " << totalLength << ", got " << ip.GetSize());
206     return -1;
207   }
208 
209   PINDEX headerLength = (ip[0]&0xf)*4; // low 4 bits in DWORDS, is this in bytes
210   payload.Attach(&ip[headerLength], totalLength-headerLength);
211 
212   m_packetSrcIP = PIPSocket::Address(4, ip+12);
213   if (!m_filterSrcIP.IsAny() && m_filterSrcIP != m_packetSrcIP)
214     return -1;
215 
216   m_packetDstIP = PIPSocket::Address(4, ip+16);
217   if (!m_filterDstIP.IsAny() && m_filterDstIP != m_packetDstIP)
218     return -1;
219 
220   // Check for fragmentation
221   bool isFragment = (ip[6] & 0x20) != 0;
222   int fragmentOffset = (((ip[6]&0x1f)<<8)+ip[7])*8;
223   PINDEX fragmentsSize = m_fragments.GetSize();
224   if (!isFragment && fragmentsSize == 0)
225     return ip[9]; // Next protocol layer
226 
227   if (fragmentsSize != fragmentOffset) {
228     PTRACE(2, "PCAPFile\tMissing IP fragment in \"" << GetFilePath() << '"');
229     m_fragments.SetSize(0);
230     return -1;
231   }
232 
233   if (fragmentsSize == 0)
234     m_fragmentProto = ip[9]; // Next protocol layer
235 
236   m_fragments.Concatenate(payload);
237 
238   if (isFragment)
239     return -1;
240 
241   payload.Attach(m_fragments, m_fragments.GetSize());
242   m_fragmentated = true;
243 
244   return m_fragmentProto; // Next protocol layer
245 }
246 
247 
GetUDP(PBYTEArray & payload)248 int OpalPCAPFile::GetUDP(PBYTEArray & payload)
249 {
250   PBYTEArray udp;
251   if (GetIP(udp) != 0x11)
252     return -1;
253 
254   if (udp.GetSize() < 8)
255     return -1;
256 
257   m_packetSrcPort = Get<PUInt16b>(udp, 0);
258   if (m_filterSrcPort != 0 && m_filterSrcPort != m_packetSrcPort)
259     return -1;
260 
261   m_packetDstPort = Get<PUInt16b>(udp, 2);
262   if (m_filterDstPort != 0 && m_filterDstPort != m_packetDstPort)
263     return -1;
264 
265   int payloadLength = udp.GetSize() - 8;
266   payload.Attach(&udp[8], payloadLength);
267   return payloadLength;
268 }
269 
270 
GetRTP(RTP_DataFrame & rtp)271 int OpalPCAPFile::GetRTP(RTP_DataFrame & rtp)
272 {
273   int packetLength = GetUDP(rtp);
274   if (packetLength < 0)
275     return -1;
276 
277   if (!rtp.SetPacketSize(packetLength))
278     return -1;
279 
280   if (rtp.GetVersion() != 2)
281     return -1;
282 
283   return rtp.GetPayloadType();
284 }
285 
286 
DiscoveredRTPInfo()287 OpalPCAPFile::DiscoveredRTPInfo::DiscoveredRTPInfo()
288 {
289   m_found[0] = m_found[1] = false;
290   m_ssrc_matches[0] = m_ssrc_matches[1] = 0;
291   m_seq_matches[0]  = m_seq_matches[1]  = 0;
292   m_ts_matches[0]   = m_ts_matches[1]   = 0;
293   m_index[0] = m_index[1] = 0;
294   m_format[0] = m_format[1] = m_type[0] = m_type[1] = "Unknown";
295 }
296 
297 
PrintOn(ostream & strm) const298 void OpalPCAPFile::DiscoveredRTPMap::PrintOn(ostream & strm) const
299 {
300   // display matches
301   const_iterator iter;
302   for (iter = begin(); iter != end(); ++iter) {
303     const DiscoveredRTPInfo & info = iter->second;
304     for (int dir = 0; dir < 2; ++dir) {
305       if (info.m_found[dir]) {
306         if (info.m_payload[dir] != info.m_firstFrame[dir].GetPayloadType())
307           strm << "Mismatched payload types" << endl;
308         strm << info.m_index[dir] << " : " << info.m_addr[dir].AsString()
309                                   << " -> " << info.m_addr[1-dir].AsString()
310                                   << ", " << info.m_payload[dir]
311                                   << " " << info.m_type[dir]
312                                   << " " << info.m_format[dir] << endl;
313       }
314     }
315   }
316 }
317 
318 
DiscoverRTP(DiscoveredRTPMap & discoveredRTPMap)319 bool OpalPCAPFile::DiscoverRTP(DiscoveredRTPMap & discoveredRTPMap)
320 {
321   if (!Restart())
322     return false;
323 
324   while (!IsEndOfFile()) {
325     RTP_DataFrame rtp;
326     if (GetRTP(rtp) < 0)
327       continue;
328 
329     // determine if reverse or forward session
330     bool dir = GetSrcIP() >  GetDstIP() ||
331               (GetSrcIP() == GetDstIP() && GetSrcPort() > GetDstPort()) ? 1 : 0;
332 
333     ostringstream keyStrm;
334     if (dir == 0)
335       keyStrm << GetSrcIP() << ':' << GetSrcPort() << '|' << GetDstIP() << ':' << GetDstPort();
336     else
337       keyStrm << GetDstIP() << ':' << GetDstPort() << '|' << GetSrcIP() << ':' << GetSrcPort();
338     string key = keyStrm.str();
339 
340     // see if we have identified this potential session before
341     DiscoveredRTPMap::iterator r;
342     if ((r = discoveredRTPMap.find(key)) == discoveredRTPMap.end()) {
343       DiscoveredRTPInfo & info = discoveredRTPMap[key];
344       info.m_addr[dir].SetAddress(GetSrcIP());
345       info.m_addr[dir].SetPort(GetSrcPort());
346       info.m_addr[1 - dir].SetAddress(GetDstIP());
347       info.m_addr[1 - dir].SetPort(GetDstPort());
348 
349       info.m_payload[dir]  = rtp.GetPayloadType();
350       info.m_seq[dir]      = rtp.GetSequenceNumber();
351       info.m_ts[dir]       = rtp.GetTimestamp();
352 
353       info.m_ssrc[dir]     = rtp.GetSyncSource();
354       info.m_seq[dir]      = rtp.GetSequenceNumber();
355       info.m_ts[dir]       = rtp.GetTimestamp();
356 
357       info.m_found[dir]    = true;
358 
359       info.m_firstFrame[dir] = rtp;
360       info.m_firstFrame[dir].MakeUnique();
361     }
362     else {
363       DiscoveredRTPInfo & info = r->second;
364       if (info.m_found[dir]) {
365         WORD seq = rtp.GetSequenceNumber();
366         DWORD ts = rtp.GetTimestamp();
367         DWORD ssrc = rtp.GetSyncSource();
368         if (info.m_ssrc[dir] == ssrc)
369           ++info.m_ssrc_matches[dir];
370         if ((info.m_seq[dir]+1) == seq)
371           ++info.m_seq_matches[dir];
372         info.m_seq[dir] = seq;
373         if ((info.m_ts[dir]+1) < ts)
374           ++info.m_ts_matches[dir];
375         info.m_ts[dir] = ts;
376       }
377       else {
378         info.m_addr[dir].SetAddress(GetSrcIP());
379         info.m_addr[dir].SetPort(GetSrcPort());
380         info.m_addr[1 - dir].SetAddress(GetDstIP());
381         info.m_addr[1 - dir].SetPort(GetDstPort());
382 
383         info.m_payload[dir]  = rtp.GetPayloadType();
384         info.m_seq[dir]      = rtp.GetSequenceNumber();
385         info.m_ts[dir]       = rtp.GetTimestamp();
386 
387         info.m_ssrc[dir]     = rtp.GetSyncSource();
388         info.m_seq[dir]      = rtp.GetSequenceNumber();
389         info.m_ts[dir]       = rtp.GetTimestamp();
390 
391         info.m_found[dir]    = true;
392 
393         info.m_firstFrame[dir] = rtp;
394         info.m_firstFrame[dir].MakeUnique();
395       }
396     }
397   }
398 
399   if (!Restart())
400     return false;
401 
402   OpalMediaFormatList formats = OpalMediaFormat::GetAllRegisteredMediaFormats();
403 
404   size_t index = 1;
405   DiscoveredRTPMap::iterator iter = discoveredRTPMap.begin();
406   while (iter != discoveredRTPMap.end()) {
407     DiscoveredRTPInfo & info = iter->second;
408     if (
409           (
410             info.m_found[0]
411             &&
412             (
413               info.m_seq_matches[0] > 5 ||
414               info.m_ts_matches[0] > 5 ||
415               info.m_ssrc_matches[0] > 5
416             )
417           )
418           ||
419           (
420             info.m_found[1]
421             &&
422             (
423               info.m_seq_matches[1] > 5 ||
424               info.m_ts_matches[1] > 5 ||
425               info.m_ssrc_matches[1] > 5
426             )
427           )
428         )
429     {
430       for (int dir = 0; dir < 2; ++dir) {
431         if (!info.m_firstFrame[dir].IsEmpty()) {
432           info.m_index[dir] = index++;
433 
434           RTP_DataFrame::PayloadTypes pt = info.m_firstFrame[dir].GetPayloadType();
435 
436           // look for known audio types
437           if (pt <= RTP_DataFrame::Cisco_CN) {
438             OpalMediaFormatList::const_iterator r;
439             if ((r = formats.FindFormat(pt, OpalMediaFormat::AudioClockRate)) != formats.end()) {
440               info.m_type[dir]   = r->GetMediaType();
441               info.m_format[dir] = r->GetName();
442             }
443           }
444 
445           // look for known video types
446           else if (pt <= RTP_DataFrame::LastKnownPayloadType) {
447             OpalMediaFormatList::const_iterator r;
448             if ((r = formats.FindFormat(pt, OpalMediaFormat::VideoClockRate)) != formats.end()) {
449               info.m_type[dir]   = r->GetMediaType();
450               info.m_format[dir] = r->GetName();
451             }
452           }
453           else {
454             // try and identify media by inspection
455             PINDEX size = info.m_firstFrame[dir].GetPayloadSize();
456             if (size > 6) {
457               const BYTE * data = info.m_firstFrame[dir].GetPayloadPtr();
458 
459               OpalMediaFormatList::const_iterator selectedFormat;
460 
461               // xxx00111 01000010 xxxx0000 - H.264
462               if ((data[0]&0x1f) == 7 && data[1] == 0x42 && (data[2]&0x0f) == 0)
463                 selectedFormat = formats.FindFormat("*h.264*");
464 
465               // 00000000 00000000 00011011 - MPEG4
466               else if (data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x01)
467                 selectedFormat = formats.FindFormat("*mpeg4*");
468 
469               // xxxxx100 00000000 100000xx  - RFC4629/H.263+
470               else if ((data[0]&0x07) == 4 && data[1] == 0x00 && (data[2]&0xfc) == 0x80) {
471                 selectedFormat = formats.FindFormat("*263P*");
472                 if (selectedFormat == formats.end()) {
473                   selectedFormat = formats.FindFormat("*263+*");
474                   if (selectedFormat == formats.end())
475                     selectedFormat = formats.FindFormat("*263*");
476                 }
477               }
478 
479               if (selectedFormat != formats.end()) {
480                 info.m_type[dir] = OpalMediaType::Video();
481                 info.m_format[dir] = selectedFormat->GetName();
482               }
483             }
484           }
485         }
486       }
487       ++iter;
488     }
489     else
490       discoveredRTPMap.erase(iter++);
491   }
492 
493   return true;
494 }
495 
496 
SetFilters(const DiscoveredRTPInfo & info,int dir)497 void OpalPCAPFile::SetFilters(const DiscoveredRTPInfo & info, int dir)
498 {
499   SetPayloadMap(info.m_payload[dir], info.m_format[dir]);
500   m_filterSrcIP = info.m_addr[dir].GetAddress();
501   m_filterDstIP = info.m_addr[1 - dir].GetAddress();
502   m_filterSrcPort = info.m_addr[dir].GetPort();
503   m_filterDstPort = info.m_addr[1 - dir].GetPort();
504 }
505 
506 
SetFilters(const DiscoveredRTPMap & discoveredRTPMap,size_t index)507 bool OpalPCAPFile::SetFilters(const DiscoveredRTPMap & discoveredRTPMap, size_t index)
508 {
509   for (DiscoveredRTPMap::const_iterator iter = discoveredRTPMap.begin();
510                                         iter != discoveredRTPMap.end(); ++iter) {
511     const OpalPCAPFile::DiscoveredRTPInfo & info = iter->second;
512     if (info.m_index[0] == index) {
513       SetFilters(info, 0);
514       return true;
515     }
516     if (info.m_index[1] == index) {
517       SetFilters(info, 1);
518       return true;
519     }
520   }
521 
522   return false;
523 }
524 
525 
SetPayloadMap(RTP_DataFrame::PayloadTypes pt,const OpalMediaFormat & format)526 bool OpalPCAPFile::SetPayloadMap(RTP_DataFrame::PayloadTypes pt, const OpalMediaFormat & format)
527 {
528   if (!format.IsTransportable())
529     return false;
530 
531   m_payloadType2mediaFormat[pt] = format;
532   m_payloadType2mediaFormat[pt].SetPayloadType(pt);
533   return true;
534 }
535 
536 
GetMediaFormat(const RTP_DataFrame & rtp) const537 OpalMediaFormat OpalPCAPFile::GetMediaFormat(const RTP_DataFrame & rtp) const
538 {
539   std::map<RTP_DataFrame::PayloadTypes, OpalMediaFormat>::const_iterator iter =
540                                           m_payloadType2mediaFormat.find(rtp.GetPayloadType());
541   return iter != m_payloadType2mediaFormat.end() ? iter->second : OpalMediaFormat();
542 }
543 
544 
545 // End Of File ///////////////////////////////////////////////////////////////
546