/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.mobicents.protocols.ss7.tools.traceparser;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import javax.xml.bind.DatatypeConverter;
/**
*
* @author sergey vetyutnev
*
*/
public class TraceReaderDriverPcap extends TraceReaderDriverBase implements TraceReaderDriver {
public TraceReaderDriverPcap(ProcessControl processControl, String fileName) {
super(processControl, fileName);
}
@Override
public void startTraceFile() throws TraceReaderException {
if (this.listeners.size() == 0)
throw new TraceReaderException("TraceReaderListener list is empty");
this.isStarted = true;
FileInputStream fis = null;
try {
if (this.processControl.checkNeedInterrupt())
return;
fis = new FileInputStream(fileName);
// 1 - LIB PCAP Global header
// typedef struct pcap_hdr_s {
// guint32 magic_number; /* magic number */
// guint16 version_major; /* major version number */
// guint16 version_minor; /* minor version number */
// gint32 thiszone; /* GMT to local correction */
// guint32 sigfigs; /* accuracy of timestamps */
// guint32 snaplen; /* max length of captured packets, in octets */
// guint32 network; /* data link type */
// } pcap_hdr_t;
// 2 - PCAP NG Section Header Block
FileEncodingType fileEncodingType;
LittleBigEndianFormat littleBigEndianFormat;
byte[] fileSignature = new byte[4];
if (fis.read(fileSignature) < 4)
throw new Exception("Not enouph data for a file signature");
byte[] libPcapSign = new byte[] { (byte) 0xd4, (byte) 0xc3, (byte) 0xb2, (byte) 0xa1 };
byte[] pcapNgSign = new byte[] { 0x0A, 0x0D, 0x0D, 0x0A };
if (Arrays.equals(fileSignature, libPcapSign)) {
fileEncodingType = FileEncodingType.LIB_PCAP;
} else if (Arrays.equals(fileSignature, pcapNgSign)) {
fileEncodingType = FileEncodingType.PCAP_NG;
} else {
throw new Exception("A file signature does not match to LIBPCAP or PCAPNG file formats");
}
switch (fileEncodingType) {
case LIB_PCAP:
littleBigEndianFormat = LittleBigEndianFormat.LITTLE_ENDIAN;
byte[] globHeader = new byte[24];
System.arraycopy(fileSignature, 0, globHeader, 0, 4);
if (fis.read(globHeader, 4, 20) < 20){
throw new Exception("Not enough data for a global header LIB PCAP");
}
// if (fis.read(globHeader) < 24)
// throw new Exception("Not enough data for a global header");
int network = ((globHeader[20] & 0xFF) << 0) + ((globHeader[21] & 0xFF) << 8) + ((globHeader[22] & 0xFF) << 16) + ((globHeader[23] & 0xFF) << 24);
int recCnt = 0;
while (fis.available() > 0) {
if (recCnt == 509) {
int gggg = 0;
gggg++;
}
if (this.processControl.checkNeedInterrupt())
return;
// Packet Header
// typedef struct pcaprec_hdr_s {
// guint32 ts_sec; /* timestamp seconds */
// guint32 ts_usec; /* timestamp microseconds */
// guint32 incl_len; /* number of octets of packet saved in file */
// guint32 orig_len; /* actual length of packet */
// } pcaprec_hdr_t;
byte[] packetHeader = new byte[16];
if (fis.read(packetHeader) < 16)
throw new Exception("Not enough data for a packet header LIB PCAP");
int ts_sec = parseIntValue(packetHeader, 0, littleBigEndianFormat);
int ts_usec = parseIntValue(packetHeader, 4, littleBigEndianFormat);
int incl_len = parseIntValue(packetHeader, 8, littleBigEndianFormat);
int orig_len = parseIntValue(packetHeader, 12, littleBigEndianFormat);
byte[] data = new byte[incl_len];
if (fis.read(data) < incl_len)
throw new Exception("Not enough data for a packet data");
recCnt++;
this.parsePacket(data, network);
}
break;
case PCAP_NG:
byte[] sectionHeaderBlockGlobHeader = new byte[24];
System.arraycopy(fileSignature, 0, sectionHeaderBlockGlobHeader, 0, 4);
if (fis.read(sectionHeaderBlockGlobHeader, 4, 20) < 20) {
throw new Exception("Not enough data for a sectionHeaderBlock Header PCAP NG");
}
byte[] bigEndianSign = new byte[] { 0x1A, 0x2B, 0x3C, 0x4D };
byte[] bigLittleEndianFld = new byte[4];
System.arraycopy(sectionHeaderBlockGlobHeader, 8, bigLittleEndianFld, 0, 4);
if (Arrays.equals(bigLittleEndianFld, bigEndianSign))
littleBigEndianFormat = LittleBigEndianFormat.BIG_ENDIAN;
else
littleBigEndianFormat = LittleBigEndianFormat.LITTLE_ENDIAN;
int blockTotalLength = parseIntValue(sectionHeaderBlockGlobHeader, 4, littleBigEndianFormat);
int sectLen = parseIntValue(sectionHeaderBlockGlobHeader, 16, littleBigEndianFormat);
byte[] sectionHeaderBlockglobOptions = new byte[blockTotalLength - 24 - 4];
if (fis.read(sectionHeaderBlockglobOptions) < sectionHeaderBlockglobOptions.length) {
throw new Exception("Not enough data for a sectionHeaderBlock Options PCAP NG");
}
byte[] blockTotalLength2 = new byte[4];
if (fis.read(blockTotalLength2) < 4) {
throw new Exception("Not enough data for a blockTotalLength2 PCAP NG");
}
PcapNgOption[] options = parsePcapNgOptions(sectionHeaderBlockglobOptions, 0, littleBigEndianFormat);
int linkType = 0;
int snapLen = 0;
recCnt = 0;
while (fis.available() > 0) {
if (this.processControl.checkNeedInterrupt())
return;
// Packet Header
byte[] packetHeader = new byte[8];
if (fis.read(packetHeader) < 8)
throw new Exception("Not enough data for a packet header PCAP NG");
int blockType = parseIntValue(packetHeader, 0, littleBigEndianFormat);
blockTotalLength = parseIntValue(packetHeader, 4, littleBigEndianFormat);
byte[] data = new byte[blockTotalLength - 12];
if (fis.read(data) < blockTotalLength - 12)
throw new Exception("Not enough data for a block data PCAP NG");
blockTotalLength2 = new byte[4];
if (fis.read(blockTotalLength2) < 4) {
throw new Exception("Not enough data for a blockTotalLength2 PCAP NG");
}
switch (blockType) {
case 1: // 0x00000001 Interface description block
linkType = parseShortValue(data, 0, littleBigEndianFormat);
snapLen = parseIntValue(data, 4, littleBigEndianFormat);
options = parsePcapNgOptions(data, 8, littleBigEndianFormat);
break;
case 6: // 0x00000006 Enhanced packet block
int _interface = parseIntValue(data, 0, littleBigEndianFormat);
long timestamp = parseLongValue(data, 4, littleBigEndianFormat);
int capturedLen = parseIntValue(data, 12, littleBigEndianFormat);
int packetLen = parseIntValue(data, 16, littleBigEndianFormat);
byte[] data2 = new byte[capturedLen];
System.arraycopy(data, 20, data2, 0, capturedLen);
int cl = ((capturedLen + 3) / 4) * 4;
options = parsePcapNgOptions(data, 20 + cl, littleBigEndianFormat);
recCnt++;
this.parsePacket(data2, linkType);
break;
}
}
break;
}
} catch (Throwable e) {
this.loger.error("General exception: " + e.getMessage());
e.printStackTrace();
throw new TraceReaderException("General exception: " + e.getMessage(), e);
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private int parseIntValue(byte[] buf, int offset, LittleBigEndianFormat littleBigEndianFormat) {
int res;
if (littleBigEndianFormat == LittleBigEndianFormat.LITTLE_ENDIAN)
res = (buf[offset + 0] & 0xFF) + (((int) buf[offset + 1] & 0xFF) << 8) + (((int) buf[offset + 2] & 0xFF) << 16)
+ (((int) buf[offset + 3] & 0xFF) << 24);
else
res = (buf[offset + 3] & 0xFF) + (((int) buf[offset + 2] & 0xFF) << 8) + (((int) buf[offset + 1] & 0xFF) << 16)
+ (((int) buf[offset + 0] & 0xFF) << 24);
return res;
}
private long parseLongValue(byte[] buf, int offset, LittleBigEndianFormat littleBigEndianFormat) {
long res;
if (littleBigEndianFormat == LittleBigEndianFormat.LITTLE_ENDIAN)
res = (buf[offset + 0] & 0xFF) + (((int) buf[offset + 1] & 0xFF) << 8) + (((int) buf[offset + 2] & 0xFF) << 16)
+ (((int) buf[offset + 3] & 0xFF) << 24) + ((buf[offset + 4] & 0xFF) << 32)
+ (((int) buf[offset + 5] & 0xFF) << 40) + (((int) buf[offset + 6] & 0xFF) << 48)
+ (((int) buf[offset + 7] & 0xFF) << 56);
else
res = (buf[offset + 7] & 0xFF) + (((int) buf[offset + 6] & 0xFF) << 8) + (((int) buf[offset + 5] & 0xFF) << 16)
+ (((int) buf[offset + 4] & 0xFF) << 24) + ((buf[offset + 3] & 0xFF) << 32)
+ (((int) buf[offset + 2] & 0xFF) << 40) + (((int) buf[offset + 1] & 0xFF) << 48)
+ (((int) buf[offset + 0] & 0xFF) << 56);
return res;
}
private int parseShortValue(byte[] buf, int offset, LittleBigEndianFormat littleBigEndianFormat) {
int res;
if (littleBigEndianFormat == LittleBigEndianFormat.LITTLE_ENDIAN)
res = (buf[offset + 0] & 0xFF) + (((int) buf[offset + 1] & 0xFF) << 8);
else
res = (buf[offset + 1] & 0xFF) + (((int) buf[offset + 0] & 0xFF) << 8);
return res;
}
public PcapNgOption[] parsePcapNgOptions(byte[] data, int ind, LittleBigEndianFormat littleBigEndianFormat) {
ArrayList<PcapNgOption> res = new ArrayList<PcapNgOption>();
while (data.length - ind >= 4) {
int optionCode = this.parseShortValue(data, ind, littleBigEndianFormat);
int optionLength = this.parseShortValue(data, ind + 2, littleBigEndianFormat);
if (optionCode == 0) {
break;
}
byte[] buf = new byte[optionLength];
System.arraycopy(data, ind + 4, buf, 0, optionLength);
PcapNgOption opt = new PcapNgOption();
opt.optionCode = optionCode;
opt.value = buf;
res.add(opt);
int ol = ((optionLength + 3) / 4) * 4;
ind += 4 + ol;
}
PcapNgOption[] ress = new PcapNgOption[res.size()];
res.toArray(ress);
return ress;
}
private void parsePacket(byte[] data, int network) throws TraceReaderException {
switch (network) {
case 1: // DLT_EN10MB
// check the min possible length
if (data == null || data.length < 34) {
return;
}
// Ethernet II level
if (data[12] != 8 || data[13] != 0) {
// this is not IP protocol - return
return;
}
byte[] ipData = new byte[data.length - 14];
System.arraycopy(data, 14, ipData, 0, data.length - 14);
this.parseIpV4Packet(ipData);
break;
case 113: // DLT_LINUX_SLL
// check the min possible length
if (data == null || data.length < 36) {
return;
}
// Ethernet II level
if (data[14] != 8 || data[15] != 0) {
// this is not IP protocol - return
return;
}
ipData = new byte[data.length - 16];
System.arraycopy(data, 16, ipData, 0, data.length - 16);
this.parseIpV4Packet(ipData);
break;
case 141: // DLT_MTP3
// check the min possible length
if (data == null || data.length < 5) {
return;
}
byte[] bufMsg = new byte[data.length + 3];
bufMsg[2] = 63;
System.arraycopy(data, 0, bufMsg, 3, data.length);
for (TraceReaderListener ls : this.listeners) {
TraceParserUtil.parceLegacyMtp3(bufMsg, this.listeners);
}
break;
}
}
private void parseIpV4Packet(byte[] data) throws TraceReaderException {
// IP protocol level
int version = (data[0] & 0xF0) >> 4; // 14
int ipHeaderLen = (data[0] & 0x0F) * 4;
if (version != 4) {
// TODO: add support for IP V6
return;
}
int ipProtocolId = data[9] & 0xFF; // 23
if (ipProtocolId != 132) { // 132 == SCTP protocol
// TODO: add support for TCP protocol
return;
}
int startSctpBlock = ipHeaderLen; // int startSctpBlock = 14 + ipHeaderLen
// SCTP
// skip SCTP header
startSctpBlock += 12;
while (true) {
// check if else sctp block exists
if (data.length < startSctpBlock + 4) // data.length < startSctpBlock + 4
break;
int blockType = data[startSctpBlock] & 0xFF;
int blockLen = ((data[startSctpBlock + 2] & 0xFF) << 8) + (data[startSctpBlock + 3] & 0xFF);
if (blockLen == 0)
break;
if (data.length < startSctpBlock + blockLen)
break;
if (blockType == 0 && blockLen > 16) {
// for m3ua blockType==0
byte[] bufM3ua = new byte[blockLen - 16];
System.arraycopy(data, startSctpBlock + 16, bufM3ua, 0, blockLen - 16);
this.parseM3uaPacket(bufM3ua);
}
int suff = blockLen % 4;
if (suff > 0)
blockLen += 4 - suff;
startSctpBlock += blockLen;
}
}
private void parseM3uaPacket(byte[] data) throws TraceReaderException {
if (data.length < 8)
return;
int version = data[0] & 0xFF;
int messageClass = data[2] & 0xFF;
int messageType = data[3] & 0xFF;
int msgLen = ((data[4] & 0xFF) << 24) + ((data[5] & 0xFF) << 16) + ((data[6] & 0xFF) << 8) + (data[7] & 0xFF);
if (data.length < msgLen)
return;
if (messageClass == 1 && messageType == 1) { // parse only transfer message - payload data
int pos = 8;
long networkAppearance = -1;
long routingContext = -1;
long correlationId = -1;
byte[] protocolData = null;
while (true) {
if (pos + 4 > msgLen)
break;
int parLen = ((data[pos + 2] & 0xFF) << 8) + (data[pos + 3] & 0xFF);
if (pos + parLen > msgLen)
break;
if (data[pos] == 0x02 && data[pos + 1] == 0x00) {
// Network Appearance
networkAppearance = ((data[pos + 4] & 0xFF) << 24) + ((data[pos + 5] & 0xFF) << 16)
+ ((data[pos + 6] & 0xFF) << 8) + (data[pos + 7] & 0xFF);
}
if (data[pos] == 0x00 && data[pos + 1] == 0x06) {
// Routing Context
routingContext = ((data[pos + 4] & 0xFF) << 24) + ((data[pos + 5] & 0xFF) << 16)
+ ((data[pos + 6] & 0xFF) << 8) + (data[pos + 7] & 0xFF);
}
if (data[pos] == 0x02 && data[pos + 1] == 0x10) {
// Protocol Data
protocolData = new byte[parLen - 4];
System.arraycopy(data, pos + 4, protocolData, 0, parLen - 4);
}
if (data[pos] == 0x00 && data[pos + 1] == 0x13) {
// Correlation Id
correlationId = ((data[pos + 4] & 0xFF) << 24) + ((data[pos + 5] & 0xFF) << 16)
+ ((data[pos + 6] & 0xFF) << 8) + (data[pos + 7] & 0xFF);
}
int parLen2 = ((parLen - 1) / 4) * 4 + 4;
pos += parLen2;
}
if (protocolData != null) {
this.parseM3uaProtocolData(networkAppearance, routingContext, correlationId, protocolData);
}
} else if (messageClass == 6 && messageType == 1) {
int len2 = ((data[18] & 0xFF) << 8) + data[19] & 0xFF;
byte[] protocolData = new byte[len2 - 4 + 3];
protocolData[2] = 63;
System.arraycopy(data, 20, protocolData, 3, protocolData.length - 3);
for (TraceReaderListener ls : this.listeners) {
TraceParserUtil.parceLegacyMtp3(protocolData, this.listeners);
}
}
}
private void parseM3uaProtocolData(long networkAppearance, long routingContext, long correlationId, byte[] data)
throws TraceReaderException {
if (data.length < 14) {
return;
}
int opc = ((data[0] & 0xFF) << 24) + ((data[1] & 0xFF) << 16) + ((data[2] & 0xFF) << 8) + (data[3] & 0xFF);
int dpc = ((data[4] & 0xFF) << 24) + ((data[5] & 0xFF) << 16) + ((data[6] & 0xFF) << 8) + (data[7] & 0xFF);
int si = data[8] & 0xFF;
int ni = data[9] & 0xFF;
int mp = data[10] & 0xFF;
int sls = data[11] & 0xFF;
byte[] bufMsg = new byte[data.length - 12];
System.arraycopy(data, 12, bufMsg, 0, data.length - 12);
for (TraceReaderListener ls : this.listeners) {
ls.ss7Message(si, ni, 0, opc, dpc, sls, bufMsg);
}
}
public enum FileEncodingType {
LIB_PCAP, PCAP_NG,
}
public enum LittleBigEndianFormat {
BIG_ENDIAN, LITTLE_ENDIAN,
}
public class PcapNgOption {
public int optionCode;
public byte[] value;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PcapNgOption=[optionCode=");
sb.append(optionCode);
sb.append(", len=");
sb.append(value.length);
sb.append(", bytes=");
sb.append(DatatypeConverter.printHexBinary(value));
sb.append(", UTF8 String=");
try {
String decoded = new String(value, "UTF-8");
sb.append(decoded);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sb.append("]");
return sb.toString();
}
}
}