nl.sidnlabs.pcap.PcapReader Maven / Gradle / Ivy
/*
* ENTRADA, a big data platform for network data analytics
*
* Copyright (C) 2016 SIDN [https://www.sidn.nl]
*
* This file is part of ENTRADA.
*
* ENTRADA 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 3 of
* the License, or (at your option) any later version.
*
* ENTRADA 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 General
* Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with ENTRADA. If
* not, see [ stream() {
Iterable valueIterable = PacketIterator::new;
return StreamSupport.stream(valueIterable.spliterator(), false);
}
/**
* Clear expired cache entries in order to avoid memory problems
*
* @param tcpCacheTTL timeout for tcp flows
* @param ipCacheTTL timeout for IP fragments
*/
public void clearCache(int tcpCacheTTL, int ipCacheTTL) {
ipDecoder.clearCache(ipCacheTTL);
tcpDecoder.clearCache(tcpCacheTTL);
}
public void close() {
try {
is.close();
} catch (IOException e) {
log.error("Error closing PCAP data inputstream", e);
}
}
private Packet nextPacket() {
byte[] pcapPacketHeader = new byte[PACKET_HEADER_SIZE];
if (!readBytes(pcapPacketHeader)) {
// no more data left
log.info("Reached end of file, or zero-length file?");
return null;
}
long packetSize =
PcapReaderUtil.convertInt(pcapPacketHeader, CAP_LEN_OFFSET, reverseHeaderByteOrder);
byte[] packetData = new byte[(int) packetSize];
if (!readBytes(packetData)) {
return Packet.NULL;
}
// find the start pos of the ip packet in the pcap frame
int ipStart = findIPStart(packetData);
if (ipStart == -1) {
if (log.isDebugEnabled()) {
log.debug("Invalid IP packet: {}", Hex.encodeHexString(packetData));
}
return Packet.NULL;
}
// the pcap header for each packet contains a timestamp with the capture time of the packet
long packetTimestampSecs =
PcapReaderUtil.convertInt(pcapPacketHeader, TIMESTAMP_OFFSET, reverseHeaderByteOrder);
long packetTimestampMicros = PcapReaderUtil
.convertInt(pcapPacketHeader, TIMESTAMP_MICROS_OFFSET, reverseHeaderByteOrder);
// decode the packet bytes
Packet decodedPacket =
ipDecoder.decode(packetData, ipStart, packetTimestampSecs, packetTimestampMicros);
packetCounter++;
return decodedPacket;
}
protected boolean validateMagicNumber(byte[] pcapHeader) {
if (PcapReaderUtil.convertInt(pcapHeader) == MAGIC_NUMBER) {
return true;
} else if (PcapReaderUtil.convertInt(pcapHeader, true) == MAGIC_NUMBER) {
reverseHeaderByteOrder = true;
return true;
} else {
return false;
}
}
protected enum LinkType {
NULL, EN10MB, RAW, LOOP, LINUX_SLL
}
protected LinkType getLinkType(long linkTypeVal) {
switch ((int) linkTypeVal) {
case 0:
return LinkType.NULL;
case 1:
return LinkType.EN10MB;
case 101:
return LinkType.RAW;
case 108:
return LinkType.LOOP;
case 113:
return LinkType.LINUX_SLL;
default:
return null;
}
}
protected int findIPStart(byte[] packet) {
int start = -1;
switch (linkType) {
case NULL:
return 4;
case EN10MB:
start = ETHERNET_HEADER_SIZE;
int etherType = PcapReaderUtil.convertShort(packet, ETHERNET_TYPE_OFFSET);
if (etherType == ETHERNET_TYPE_8021Q) {
etherType = PcapReaderUtil.convertShort(packet, ETHERNET_TYPE_OFFSET + 4);
start += 4;
}
if (etherType == ETHERNET_TYPE_IP || etherType == ETHERNET_TYPE_IPV6)
return start;
break;
case RAW:
return 0;
case LOOP:
return 4;
case LINUX_SLL:
return SLL_HEADER_BASE_SIZE
+ PcapReaderUtil.convertShort(packet, SLL_ADDRESS_LENGTH_OFFSET);
}
return -1;
}
protected boolean readBytes(byte[] buf) {
try {
is.readFully(buf);
} catch (EOFException e) {
// Reached the end of the stream
caughtEOF = true;
return false;
} catch (IOException e) {
log.error("Error while reading " + buf.length + " bytes from buffer");
return false;
}
return true;
}
public Map getFlows() {
return tcpDecoder.getFlows();
}
public void setFlows(Map flows) {
tcpDecoder.setFlows(flows);
}
private class PacketIterator implements Iterator {
private Packet next;
private void fetchNext() {
if (next == null) {
// 1st check if reassembled response is available
// if (tcpDecoder.hasReassembledPackets()) {
// next = tcpDecoder.getNextReassmbledPacket();
// reassembledPacketCounter++;
// return;
// }
// skip fragmented packets until they are assembled
do {
try {
next = nextPacket();
} catch (Exception e) {
log.error("PCAP decode error: ", e);
next = Packet.NULL;
}
} while (next == Packet.NULL);
}
}
@Override
public boolean hasNext() {
// fetchNext will keep result in "next" var so that when next() is
// called the data does not have to be parsed a 2nd time
fetchNext();
if (next != null)
return true;
// // there might still be a reassembled packet in the tcpdecoder waiting to
// // be fetched.
// if (tcpDecoder.hasReassembledPackets()) {
// return true;
// }
// no more data left
int remainingFlows = tcpDecoder.getFlows().size() + ipDecoder.getDatagrams().size();
if (remainingFlows > 0) {
log.warn("Still " + remainingFlows + " flows queued. Missing packets to finish assembly?");
log.warn("Packets processed: " + packetCounter);
log.warn("Reassembled response packets: " + reassembledPacketCounter);
}
return false;
}
@Override
public Packet next() {
fetchNext();
if (next == null) {
throw new NoSuchElementException("No more packets to decode");
}
try {
return next;
} finally {
// make sure to set next to null so the next packet is read from the stream
next = null;
}
}
@Override
public void remove() {
// Not supported
}
}
public Multimap getDatagrams() {
return ipDecoder.getDatagrams();
}
public void setDatagrams(Multimap map) {
ipDecoder.setDatagrams(map);
}
}