package com.netifera.platform.net.internal.sniffing.file; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import com.netifera.platform.net.pcap.Datalink; public class PcapCaptureFile implements IPcapCaptureFile { /** Maximum possible packet size */ private final static int MAX_PACKET_SIZE = (64 * 1024); // MaxSizeOf(IPv4) private final static boolean debug = false; private final File captureFile; private final String captureFilePath; private final long captureFileLength; private boolean opened; private FileChannel inputChannel; private boolean bigEndian; private ByteBuffer headerBuffer; private Datalink linkType; private ByteBuffer packetBuffer; private int packetCount; private boolean foundTruncated; class CaptureRecord implements ICaptureFileRecord { private final long seconds; private final int useconds; private final int captureLength; private final int originalLength; private final ByteBuffer recordBytes; CaptureRecord(long seconds, int useconds, int captureLen, int originalLength, ByteBuffer data) { this.seconds = seconds; this.useconds = useconds; this.captureLength = captureLen; this.originalLength = originalLength; this.recordBytes = data; } public int getCaptureLength() { return captureLength; } public int getOriginalLength() { return originalLength; } public ByteBuffer getRecordBytes() { return recordBytes; } public int getCaplen() { return captureLength; } public int getDatalen() { return originalLength; } public int getMicroseconds() { return useconds; } public long getSeconds() { return seconds; } } /** * Create a new <code>PcapCaptureFile</code> for the specified file. * @param path Path to the capture file. */ public PcapCaptureFile(String path) { captureFile = new File(path); captureFilePath = lookupPath(); captureFileLength = captureFile.length(); } private String lookupPath() { try { return captureFile.getCanonicalPath(); } catch (IOException e) { return "PATH LOOKUP FAILED: " + e.getMessage(); } } public String getPath() { return captureFilePath; } public int getProgress() { if(!opened) { return 0; } try { return (int) ((inputChannel.position() * 100) / captureFileLength); } catch (IOException e) { return 0; } } public int getPacketCount() { return packetCount; } /* * (non-Javadoc) * @see com.netifera.platform.net.internal.sniffing.file.IPcapCaptureFile#open() */ public void open() throws CaptureFileException { if(opened) { throw new CaptureFileException("Cannot 'reopen' a capture file object"); } try { inputChannel = new FileInputStream(captureFile).getChannel(); readCaptureHeader(); if(debug) { System.out.println("Tcpdump capture file version 2.4"); System.out.println("snaplen: " + packetBuffer.capacity()); System.out.println("linktype: " + linkType); } } catch (FileNotFoundException e) { throw new CaptureFileException("Unable to open '" + captureFile.getName() + "' for reading.", e); } catch (IOException e) { try { if(inputChannel != null) { inputChannel.close(); } } catch (IOException e1) {} throw new CaptureFileException("I/O error reading capture file header", e); } opened = true; packetCount = 0; } /** * Read the main header from the capture file sets up the correct state for reading the rest of * the file. A packet decoder is selected depending on the link layer type and a packet buffer * is allocated depending on the size of the snaplen. * * @throws IOException An I/O error has occurred while reading the header data. * @throws CaptureFileException The header could not be read and parsed. */ private void readCaptureHeader() throws IOException, CaptureFileException { ByteBuffer magicBuffer = ByteBuffer.allocate(8); while(magicBuffer.hasRemaining()) { if(inputChannel.read(magicBuffer) == -1) { inputChannel.close(); throw new CaptureFileException("EOF reading magic value from capture file header"); } } magicBuffer.flip(); if(!validMagic(magicBuffer)) { inputChannel.close(); throw new CaptureFileException("File is not a valid libpcap capture file"); } headerBuffer = ByteBuffer.allocate(16); if(!bigEndian) headerBuffer.order(ByteOrder.LITTLE_ENDIAN); if(!readHeaderBuffer()) { throw new CaptureFileException("Unexpected EOF reading capture file header."); } headerBuffer.getInt(); /* discard thiszone field */ headerBuffer.getInt(); /* discard sigfigs field */ final int snaplen = headerBuffer.getInt(); final int dlt = headerBuffer.getInt(); linkType = dltLookup(dlt); allocatePacketBuffer(snaplen); } public int getSnaplen() { if(packetBuffer != null) { return packetBuffer.capacity(); } else { return 0; } } public Datalink getLinkType() { return linkType; } /** * Allocate a buffer for receiving raw packet data. * @param size The requested size for the buffer. * @throws CaptureFileException Thrown if an illegal size is requested. */ private void allocatePacketBuffer(int size) throws CaptureFileException { if(size < 0 || size > MAX_PACKET_SIZE) { throw new CaptureFileException("Packet buffer size " + size + " is too large"); } packetBuffer = ByteBuffer.allocate(size); } /** * Fill the header buffer with a 16 byte record header. * @return False on EOF. True otherwise. * @throws CaptureFileException Thrown if reading the header causes an I/O error. */ private boolean readHeaderBuffer() throws CaptureFileException { headerBuffer.clear(); try { while(headerBuffer.hasRemaining()) { if(inputChannel.read(headerBuffer) <= 0) { return false; } } } catch (IOException e) { try { inputChannel.close(); } catch (IOException e1) { } throw new CaptureFileException("I/O error reading capture file header", e); } headerBuffer.flip(); return true; } private Datalink dltLookup(int n) { for(Datalink dlt : Datalink.values()) { if(dlt.getConstant() == n) { return dlt; } } return Datalink.DLT_INVALID; } /** * Read and return a single capture file record from the packet capture file. * * @return The next capture file record or <code>null</code> if EOF is reached. * @throws CaptureFileException Thrown for any error encountered while proccesing the capture file. */ public ICaptureFileRecord readRecord() throws CaptureFileException { if(!readHeaderBuffer()) { return null; } final int timestampSeconds = headerBuffer.getInt(); final int timestampMicroseconds = headerBuffer.getInt(); final int captureLength = headerBuffer.getInt(); final int originalLength = headerBuffer.getInt(); if(captureLength > MAX_PACKET_SIZE) { throw new CaptureFileException("Capture length of " + captureLength + " decoded from record header exceeds the maximum allowed packet size of " + MAX_PACKET_SIZE); } if(captureLength > packetBuffer.capacity()) { if(debug) { System.out.println("Warning: Record length exceeds snaplen. Extending read buffer..."); } allocatePacketBuffer(captureLength); } if(originalLength > captureLength) { if(!foundTruncated) { if(debug) { System.out.println("Capture file contains truncated packets."); } foundTruncated = true; } } packetBuffer.clear(); packetBuffer.limit(captureLength); while(packetBuffer.hasRemaining()) { try { if(inputChannel.read(packetBuffer) == -1) { inputChannel.close(); throw new CaptureFileException("Premature EOF reading capture file"); } } catch (IOException e) { try { inputChannel.close(); } catch (IOException e1) {} throw new CaptureFileException("I/O error reading capture file", e); } } packetCount++; packetBuffer.flip(); return new CaptureRecord( timestampSeconds, timestampMicroseconds, captureLength, originalLength, packetBuffer.asReadOnlyBuffer()); } /** * Process the first 8 bytes of the capture file to determine if the 'magic' and 'version' fields * have legal values. The endianess of the capture file is inferred from the byte order of the * magic and version fields. The member <code>bigEndian</code> is set appropriately depending on * the observed byte ordering. * * Only version value 2.4 is recognized as legal. * * @param magicBuffer This buffer is the first 8 bytes from the capture file header and contains both the magic and version fields. * @return Return true if the header is valid, false otherwise. */ private boolean validMagic(ByteBuffer magicBuffer) { int magic = magicBuffer.getInt(); int version = magicBuffer.getInt(); if(debug) { System.out.println("magic is " + Integer.toHexString(magic)); System.out.println("version is " + Integer.toHexString(version)); } if( (magic == 0xa1b2c3d4) && (version == 0x00020004) ) { bigEndian = true; return true; } else if( (magic == 0xd4c3b2a1) && (version == 0x02000400) ) { bigEndian = false; return true; } else { return false; } } }