diff options
author | Damian Minkov <damencho@jitsi.org> | 2010-12-03 08:53:04 +0000 |
---|---|---|
committer | Damian Minkov <damencho@jitsi.org> | 2010-12-03 08:53:04 +0000 |
commit | a21b3d7353c9fc9ed47288333d3f793b01ab5689 (patch) | |
tree | 5b8210d76dc28c82998188252958e6c6beaec96a /src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java | |
parent | 4785e35f694e73a65b6163f31c9aac4f58aa925f (diff) | |
download | jitsi-a21b3d7353c9fc9ed47288333d3f793b01ab5689.zip jitsi-a21b3d7353c9fc9ed47288333d3f793b01ab5689.tar.gz jitsi-a21b3d7353c9fc9ed47288333d3f793b01ab5689.tar.bz2 |
Introduce Packet Logging Service with its configuration form and implementation for sip,jabber and rtp.
Diffstat (limited to 'src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java')
-rw-r--r-- | src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java | 844 |
1 files changed, 844 insertions, 0 deletions
diff --git a/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java b/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java new file mode 100644 index 0000000..924bbdb --- /dev/null +++ b/src/net/java/sip/communicator/impl/packetlogging/PacketLoggingServiceImpl.java @@ -0,0 +1,844 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.packetlogging; + +import net.java.sip.communicator.service.packetlogging.*; +import net.java.sip.communicator.util.*; + +import java.io.*; +import java.util.*; + +/** + * Packet Logging Service implementation dumping logs in + * pcap(tcpdump/wireshark) format file. + * + * @author Damian Minkov + */ +public class PacketLoggingServiceImpl + implements PacketLoggingService +{ + /** + * Our Logger. + */ + private static final Logger logger + = Logger.getLogger(PacketLoggingServiceImpl.class); + + /** + * The OutputStream we are currently writing to. + */ + private FileOutputStream outputStream = null; + + /** + * The thread that queues packets and saves them to file. + */ + private SaverThread saverThread = new SaverThread(); + + /** + * The fake ethernet header we use as template. + */ + private final static byte[] fakeEthernetHeader = + new byte[]{ + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x08, (byte)0x00 + }; + + /** + * The fake ipv4 header we use as template. + */ + private final static byte[] ipHeaderTemplate = + new byte[]{ + (byte)0x45, (byte)0x00, + (byte)0x03, (byte)0x48, (byte)0xc9, (byte)0x14, + (byte)0x00, (byte)0x00, (byte)0x35,(byte)0x11, + (byte)0x00, (byte)0x00, // check sum + (byte)0xd5, (byte)0xc0, (byte)0x3b, (byte)0x4b,//src + (byte)0xc0, (byte)0xa8, (byte)0x00, (byte)0x34 //dst + }; + + /** + * The fake ipv6 header we use as template. + */ + private final static byte[] ip6HeaderTemplate = + new byte[]{ + (byte)0x60, (byte)0x00, (byte)0x00, (byte)0x00, // version, traffic, flowable + (byte)0x00, (byte)0x00, // length + (byte)0x11, // next header + (byte)0x40, // hop limit + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // src + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // src + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // src + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // src + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // dst + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // dst + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // dst + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 // dst + }; + + /** + * The fake udp header we use as template. + */ + private final static byte[] udpHeaderTemplate = + new byte[]{ + (byte)0x13, (byte)0xc4, + (byte)0x13, (byte)0xc4, + (byte)0x03, (byte)0x34, + (byte)0x00, (byte)0x00// checksum + + }; + + /** + * The fake tcp header we use as template. + */ + private final static byte[] tcpHeaderTemplate = + new byte[]{ + (byte)0xb7, (byte)0x61, // src port + (byte)0x13, (byte)0xc4, // dst port + (byte)0x4f, (byte)0x20, (byte)0x37, (byte)0x3b, // seq number + (byte)0x11, (byte)0x1d, (byte)0xbc, (byte)0x54, // ack number + (byte)0x80, // header length + (byte)0x18, // flags + (byte)0x00, (byte)0x2e, // windows size + (byte)0xac, (byte)0x78, // check sum + (byte)0x00, (byte)0x00, + (byte)0x01, (byte)0x01, (byte)0x08, (byte)0x0a, // options + (byte)0x00, (byte)0x06, (byte)0xd4, (byte)0x48, // options + (byte)0x6e, (byte)0xcc, (byte)0x76, (byte)0xbd // options + }; + + /** + * Using this object to lock and protectd the two counters + * used for tcp seq and ack numbers. + */ + private Object tcpCounterLock = new Object(); + + /** + * The seq that the sender will send. + */ + private long srcCount = 1; + + /** + * This is the ack number send from the sender. + */ + private long dstCount = 1; + + /** + * A counter watching how much has been written to the file. + */ + private long written = 0; + + /** + * The limit for the file size. + * 0 means no limit. + */ + private long limit = 5000000; + + /** + * The counter for number of files. + */ + private int logfileCount = 3; + + /** + * All the files we can use for writing. + */ + private File[] files; + + /** + * Starting the packet logger. Generating the files we can use, + * rotate any previous files and open the current file for writing. + */ + public void start() + { + limit = PacketLoggingActivator.getConfigurationService().getLong( + PacketLoggingActivator.PACKET_LOGGING_FILE_SIZE_PROPERTY_NAME, + limit); + + logfileCount = PacketLoggingActivator.getConfigurationService().getInt( + PacketLoggingActivator.PACKET_LOGGING_FILE_COUNT_PROPERTY_NAME, + logfileCount); + + saverThread.start(); + } + + /** + * Generates the files we will later use for writing. + * @throws Exception + */ + private void getFileNames() + throws Exception + { + files = new File[getLogfileCount()]; + for(int i = 0; i < getLogfileCount(); i++) + { + files[i] = PacketLoggingActivator.getFileAccessService() + .getPrivatePersistentFile("log/sip-communicator" + i + ".pcap"); + } + } + + /** + * Rotates any existing files and use the newly created first one + * for writing. + * @throws IOException + */ + private void rotateFiles() + throws IOException + { + if(outputStream != null) + { + outputStream.flush(); + outputStream.close(); + } + + for (int i = getLogfileCount() -2; i >= 0; i--) + { + File f1 = files[i]; + File f2 = files[i+1]; + if (f1.exists()) + { + if (f2.exists()) + { + f2.delete(); + } + f1.renameTo(f2); + } + } + + outputStream = new FileOutputStream(files[0]); + written = 0; + createGlobalHeader(); + } + + /** + * Stops the packet logging. + */ + public void stop() + { + saverThread.stopRunning(); + + if(outputStream != null) + { + try + { + outputStream.flush(); + outputStream.close(); + } + catch(IOException e) + { + e.printStackTrace(); + } + outputStream = null; + } + } + + /** + * Creates pcap file global header. + * @throws IOException + */ + private void createGlobalHeader() + throws IOException + { + /* magic number(swapped) */ + outputStream.write(0xd4); + outputStream.write(0xc3); + outputStream.write(0xb2); + outputStream.write(0xa1); + + /* major version number */ + outputStream.write(0x02); + outputStream.write(0x00); + + /* minor version number */ + outputStream.write(0x04); + outputStream.write(0x00); + + /* GMT to local correction */ + outputStream.write(0x00); + outputStream.write(0x00); + outputStream.write(0x00); + outputStream.write(0x00); + + /* accuracy of timestamps */ + outputStream.write(0x00); + outputStream.write(0x00); + outputStream.write(0x00); + outputStream.write(0x00); + + /* max length of captured packets, in octets */ + outputStream.write(0xff); + outputStream.write(0xff); + outputStream.write(0x00); + outputStream.write(0x00); + + /* data link type(ethernet) */ + outputStream.write(0x01); + outputStream.write(0x00); + outputStream.write(0x00); + outputStream.write(0x00); + } + + /** + * Checks is logging globally enabled for the service. + * + * @return is logging enabled. + */ + public boolean isLoggingEnabled() + { + return PacketLoggingActivator.isGlobalLoggingEnabled(); + } + + /** + * Checks is logging globally enabled for and is it currently + * available fo the given service. + * + * @param protocol that is checked. + * @return is logging enabled. + */ + public boolean isLoggingEnabled(ProtocolName protocol) + { + switch(protocol) + { + case SIP: + return PacketLoggingActivator.isGlobalLoggingEnabled() + && PacketLoggingActivator.isSipLoggingEnabled(); + case JABBER: + return PacketLoggingActivator.isGlobalLoggingEnabled() + && PacketLoggingActivator.isJabberLoggingEnabled(); + case RTP: + return PacketLoggingActivator.isGlobalLoggingEnabled() + && PacketLoggingActivator.isRTPLoggingEnabled(); + default: + return false; + } + } + + /** + * Log a packet with all the required information. + * + * @param protocol the source protocol that logs this packet. + * @param sourceAddress the source address of the packet. + * @param sourcePort the source port of the packet. + * @param destinationAddress the destination address. + * @param destinationPort the destination port. + * @param transport the transport this packet uses. + * @param sender are we the sender of the packet or not. + * @param packetContent the packet content. + */ + public void logPacket( + ProtocolName protocol, + byte[] sourceAddress, int sourcePort, + byte[] destinationAddress, int destinationPort, + TransportName transport, + boolean sender, + byte[] packetContent) + { + this.logPacket(protocol, sourceAddress, sourcePort, + destinationAddress, destinationPort, + transport, sender, + packetContent, 0, packetContent.length); + } + + /** + * Log a packet with all the required information. + * + * @param protocol the source protocol that logs this packet. + * @param sourceAddress the source address of the packet. + * @param sourcePort the source port of the packet. + * @param destinationAddress the destination address. + * @param destinationPort the destination port. + * @param transport the transport this packet uses. + * @param sender are we the sender of the packet or not. + * @param packetContent the packet content. + * @param packetOffset the packet content offset. + * @param packetLength the packet content length. + */ + public void logPacket( + ProtocolName protocol, + byte[] sourceAddress, + int sourcePort, + byte[] destinationAddress, + int destinationPort, + TransportName transport, + boolean sender, + byte[] packetContent, + int packetOffset, + int packetLength) + { + saverThread.queuePacket( + new Packet(protocol, + sourceAddress, + sourcePort, + destinationAddress, + destinationPort, + transport, + sender, + packetContent, + packetOffset, + packetLength)); + } + + /** + * Dump the packet to the output file stream. + * + * @param packet the packet ot save. + * @throws Exception when error occurs saving to file stream or when + * rotating files. + */ + private void savePacket(Packet packet) + throws Exception + { + // if one of the addresses is ipv4 we are using ipv4, + // local udp addresses come as 0.0.0.0.0....0.0.0 when + // ipv6 is enabled in the underlying os + boolean isIPv4 = packet.sourceAddress.length == 4 + || packet.destinationAddress.length == 4; + + byte[] ipHeader; + + if(isIPv4) + { + ipHeader = new byte[ipHeaderTemplate.length]; + System.arraycopy( + ipHeaderTemplate, 0, ipHeader, 0, ipHeader.length); + System.arraycopy(packet.sourceAddress, + 0, + ipHeader, + 12, + 4); + System.arraycopy(packet.destinationAddress, + 0, + ipHeader, + 16, + 4); + } + else + { + ipHeader = new byte[ip6HeaderTemplate.length]; + System.arraycopy( + ip6HeaderTemplate, 0, ipHeader, 0, ipHeader.length); + System.arraycopy(packet.sourceAddress, + 0, + ipHeader, + 8, + 16); + + System.arraycopy(packet.destinationAddress, + 0, + ipHeader, + 24, + 16); + } + + byte[] transportHeader; + short len; + if(packet.transport == TransportName.UDP) + { + byte[] udpHeader = new byte[udpHeaderTemplate.length]; + transportHeader = udpHeader; + System.arraycopy(udpHeaderTemplate, 0, + udpHeader, 0, udpHeader.length); + + writeShort(packet.sourcePort, udpHeader, 0); + writeShort(packet.destinationPort, udpHeader, 2); + len = (short)(packet.packetLength + udpHeader.length); + writeShort(len, udpHeader, 4); + } + else + { + transportHeader = new byte[tcpHeaderTemplate.length]; + System.arraycopy(tcpHeaderTemplate, 0, transportHeader, + 0, transportHeader.length); + + writeShort(packet.sourcePort, transportHeader, 0); + writeShort(packet.destinationPort, transportHeader, 2); + + len = (short)(packet.packetLength + transportHeader.length); + + if(packet.sender) + { + long seqnum; + long acknum; + synchronized(tcpCounterLock) + { + seqnum = srcCount; + srcCount += packet.packetLength; + acknum = dstCount; + } + + intToBytes((int)(seqnum & 0xffffffff), + transportHeader, 4); + intToBytes((int)(acknum & 0xffffffff), + transportHeader, 8); + } + else + { + long seqnum; + long acknum; + synchronized(tcpCounterLock) + { + seqnum = dstCount; + dstCount += packet.packetLength; + acknum = srcCount; + } + + intToBytes((int)(seqnum & 0xffffffff), + transportHeader, 4); + intToBytes((int)(acknum & 0xffffffff), + transportHeader, 8); + } + } + + // now set ip header total length + if(isIPv4) + { + short ipTotalLen = (short)(len + ipHeader.length); + writeShort(ipTotalLen, ipHeader, 2); + + if(packet.transport == TransportName.UDP) + ipHeader[9] = (byte)0x11; + else + ipHeader[9] = (byte)0x06; + + int chk2 = computeChecksum(ipHeader); + ipHeader[10] = (byte) (chk2 >> 8); + ipHeader[11] = (byte) (chk2 & 0xff); + } + else + { + writeShort(len, ipHeader, 4); + + if(packet.transport == TransportName.UDP) + ipHeader[6] = (byte)0x11; + else + ipHeader[6] = (byte)0x06; + } + + long current = System.currentTimeMillis(); + int tsSec = (int)(current/1000); + int tsUsec = (int)((current%1000) * 1000); + int feakHeaderLen = fakeEthernetHeader.length + + ipHeader.length + transportHeader.length; + int inclLen = packet.packetLength + feakHeaderLen; + int origLen = inclLen; + + synchronized(this) + { + // open files only if needed + if(outputStream == null) + { + getFileNames(); + rotateFiles();// this one opens the file for write + } + + if(getLimit() > 0 && written > getLimit()) + rotateFiles(); + + addInt(tsSec); + addInt(tsUsec); + addInt(inclLen); + addInt(origLen); + + outputStream.write(fakeEthernetHeader); + outputStream.write(ipHeader); + outputStream.write(transportHeader); + outputStream.write( + packet.packetContent, + packet.packetOffset, + packet.packetLength); + outputStream.flush(); + + written += inclLen + 16; + } + } + + /** + * Writes int to the file. Used for packet headers. + * @param d the value to write. + * @throws IOException + */ + private void addInt(int d) + throws IOException + { + outputStream.write ((d & 0xff)); + outputStream.write(((d & 0xff00) >> 8)); + outputStream.write(((d & 0xff0000) >> 16)); + outputStream.write(((d & 0xff000000) >> 24)); + } + + /** + * Converts a 32-bit word representation of an IPv4 address to a + * byte array. + * + * @param address The 32-bit word representation of the IPv4 address. + * @param data The byte array in which to store the IPv4 data. + * @param offset The offset into the array where the data start. + */ + private static final void intToBytes(int address, byte[] data, + int offset) + { + data[offset] = (byte)(0xff & (address >>> 24)); + data[offset + 1] = (byte)(0xff & (address >>> 16)); + data[offset + 2] = (byte)(0xff & (address >>> 8)); + data[offset + 3] = (byte)(0xff & address); + } + + /** + * Puts the short value to the array. + * @param value value to convert to bytes. + * @param data destination data + * @param offset offset in the data + */ + private static void writeShort(int value, byte[] data, int offset) + { + data[offset] = (byte) (value >> 8); + data[offset + 1] = (byte) value; + } + + /** + * Calculates checksums assuming the checksum is a 16-bit header field. + */ + private int computeChecksum(byte[] data) + { + int total = 0; + int i = 0; + + // Don't Skip existing checksum cause its set to 0000 + int imax = data.length - (data.length % 2); + + while(i < imax) + total+=(((data[i++] & 0xff) << 8) | (data[i++] & 0xff)); + + if(i < data.length) + total+=((data[i] & 0xff) << 8); + + // Fold to 16 bits + while((total & 0xffff0000) != 0) + total = (total & 0xffff) + (total >>> 16); + + total = (~total & 0xffff); + + return total; + } + + /** + * The limit for the file size. 0 means no limit. + * @return the file size limit. + */ + public long getLimit() + { + return limit; + } + + /** + * Changes the file size limit. + * @param limit the new limit size. + */ + public void setLimit(long limit) + { + this.limit = limit; + + PacketLoggingActivator.getConfigurationService().setProperty( + PacketLoggingActivator.PACKET_LOGGING_FILE_SIZE_PROPERTY_NAME, + limit); + } + + /** + * The counter for number of files. + * @return the number of file counts. + */ + public int getLogfileCount() + { + return logfileCount; + } + + /** + * Changes file count. + * @param logfileCount the new file count. + */ + public void setLogfileCount(int logfileCount) + { + this.logfileCount = logfileCount; + + PacketLoggingActivator.getConfigurationService().setProperty( + PacketLoggingActivator.PACKET_LOGGING_FILE_COUNT_PROPERTY_NAME, + logfileCount); + } + + /** + * The data we receive and that we will dump in a file. + */ + private class Packet + { + /** + * The protocol logging this packet. + */ + ProtocolName protocol; + + /** + * The source address of the packet. + */ + byte[] sourceAddress; + + /** + * The source port of the packet. + */ + int sourcePort; + + /** + * The destination address of the packet. + */ + byte[] destinationAddress; + + /** + * The destination port of the packet. + */ + int destinationPort; + + /** + * Is the packet a udp one. + */ + TransportName transport; + + /** + * Are we sending the packet, or false if we are receiving. + */ + boolean sender; + + /** + * Array containing packet content. + */ + byte[] packetContent; + + /** + * The offset in the packetContent where packet content is. + */ + int packetOffset; + + /** + * The length of the packet content. + */ + int packetLength; + + /** + * Creates a packet with the needed data. + * @param protocol the source protocol that logs this packet. + * @param sourceAddress The source address of the packet. + * @param sourcePort The source port of the packet. + * @param destinationAddress The destination address of the packet. + * @param destinationPort The destination port of the packet. + * @param transport the transport this packet uses. + * @param sender Are we sending the packet, + * or false if we are receiving. + * @param packetContent Array containing packet content. + * @param packetOffset The offset in the packetContent + * where packet content is. + * @param packetLength The length of the packet content. + */ + private Packet(ProtocolName protocol, + byte[] sourceAddress, + int sourcePort, + byte[] destinationAddress, + int destinationPort, + TransportName transport, + boolean sender, + byte[] packetContent, + int packetOffset, + int packetLength) + { + this.protocol = protocol; + this.sourceAddress = sourceAddress; + this.sourcePort = sourcePort; + this.destinationAddress = destinationAddress; + this.destinationPort = destinationPort; + this.transport = transport; + this.sender = sender; + this.packetContent = packetContent; + this.packetOffset = packetOffset; + this.packetLength = packetLength; + } + } + + /** + * Dumps packet in separate thread so we don't block + * our calling thread. + */ + private class SaverThread + extends Thread + { + /** + * start/stop indicator. + */ + private boolean stopped = true; + + /** + * List of packets queued to be written in the file. + */ + private List<Packet> packetsToDump = new ArrayList<Packet>(); + + /** + * Sends instant messages in separate thread so we don't block + * our calling thread. + */ + public void run() + { + stopped = false; + + while(!stopped) + { + Packet pktToSave = null; + + synchronized(this) + { + if(packetsToDump.isEmpty()) + { + try + { + wait(); + } + catch (InterruptedException iex) + { + } + } + + if(!packetsToDump.isEmpty()) + pktToSave = packetsToDump.remove(0); + } + + if(pktToSave != null) + { + try + { + savePacket(pktToSave); + } + catch(Throwable t) + { + logger.error("Error writing packet to file", t); + } + } + } + } + + /** + * Interrupts this sender so that it would no longer send messages. + */ + public synchronized void stopRunning() + { + stopped = true; + notifyAll(); + } + + /** + * Schedule new packet for save. + * @param packet new packet to save. + */ + public synchronized void queuePacket(Packet packet) + { + packetsToDump.add(packet); + notifyAll(); + } + } +} |