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