/** * Copyright 2007-2015, Kaazing Corporation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kaazing.k3po.pcap.converter.internal.parser; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Locale; import java.util.Stack; import java.util.logging.Logger; import org.gjt.xpp.XmlPullParser; import org.gjt.xpp.XmlPullParserException; import org.gjt.xpp.XmlPullParserFactory; import org.gjt.xpp.XmlStartTag; import org.kaazing.k3po.pcap.converter.internal.packet.Packet; /** * PdmlParser will parse tcpdumps by converting them into a pdml, It will then return a Packet that states the relevant * properties of what was just read * */ public class Parser { private final static Logger LOG = Logger.getLogger(Parser.class.getName()); private final XmlPullParser parser; private final TcpdumpReader tcpdumpReader; private final Stack<String> protoStack = new Stack<>(); private final Stack<String> fieldStack = new Stack<>(); private static XmlPullParserFactory xppFactory; private int packetsCnted = 0; /** * Initializes a PdmlParser Object from a tcpdump file location, during the construction of the object a pdml file * will be generated * @param tcpDumpFileLocation an absolutePath to the tcpdump file * @param pdmlFileDestination an absolute path to where a pdml file will be generated * @param tsharkPath an absolute path to where tshark can be called from the runtime */ public Parser(InputStream tcpdumpInputStream, InputStream pdmlInputStream, String tsharkPath) { try { xppFactory = XmlPullParserFactory.newInstance(); xppFactory.setNamespaceAware(false); parser = xppFactory.newPullParser(); parser.setInput(new InputStreamReader(pdmlInputStream)); } catch (XmlPullParserException e) { e.printStackTrace(); throw new ParserFailureException("Failed to init parser: " + e.getMessage()); } tcpdumpReader = new TcpdumpReader(tcpdumpInputStream); } /** * Constructor that will default the tshark location to tshark (ie assuming started with that set as an environment * variable, which will not be true if running from eclipse) * @param tcpDumpFileLocation * @param pdmlFileDestination */ public Parser(InputStream tcpdumpInputStream, InputStream pdmlInputStream) { this(tcpdumpInputStream, pdmlInputStream, "tshark"); } /** * Returns the next Packet that can be parsed, or null if all packets have been read * @return Packet */ public Packet getNextPacket() { Packet parsedPacket = parseNextPacketFromPdml(); if ( parsedPacket == null ) { // All packets have been read return null; } parsedPacket = addTcpdumpInfoToPacket(parsedPacket); return parsedPacket; } /** * Adds tcpdump info onto packet parsed from pdml i.e. adds the packet payload for various protocols * @param parsedPacket * @return */ private Packet addTcpdumpInfoToPacket(Packet parsedPacket){ int packetSize = parsedPacket.getPacketSize(); tcpdumpReader.readNewPacket(packetSize); // Get Tcp Payload if ( parsedPacket.isTcp() ) { int payloadSize = parsedPacket.getTcpPayloadSize(); int payloadStart = parsedPacket.getTcpPayloadStart(); parsedPacket.setTcpPayload(tcpdumpReader.getPayload(payloadStart, payloadSize)); } tcpdumpReader.packetReadComplete(); return parsedPacket; } /** * Returns the Packet set with all properties that can be read from the pdml * @return */ private Packet parseNextPacketFromPdml() { Packet currentPacket = null; int eventType; fieldStack.empty(); try { eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { switch (eventType) { case XmlPullParser.START_TAG: if ( parser.getRawName().contains("packet") ) { // At packet tag currentPacket = setPacketProperties(xppFactory.newStartTag()); } else if ( parser.getRawName().contains("proto") ) { currentPacket = setProtoProperties(xppFactory.newStartTag(), currentPacket); } else if ( parser.getRawName().contains("field") ) { fieldStack.push("field"); // Added http after all others if (protoStack.peek().equals("http") && currentPacket.isTcp()){ currentPacket = setHttpFieldProperties(xppFactory.newStartTag(), currentPacket); } else{ currentPacket = setFieldProperties(xppFactory.newStartTag(), currentPacket); } } else { ; } break; case XmlPullParser.END_TAG: if ( parser.getRawName().contains("packet") ) { eventType = parser.next(); return currentPacket; } else if ( parser.getRawName().contains("proto") ) { protoStack.pop(); } else if ( parser.getRawName().contains("field") ) { fieldStack.pop(); } default: } eventType = parser.next(); } return null; // Returned if at end of pdml } catch (XmlPullParserException e) { e.printStackTrace(); throw new ParserFailureException("Failed parsing pmdl " + e); } catch (IOException e) { e.printStackTrace(); throw new ParserFailureException("Failed reading parser.next " + e); } } private Packet setPacketProperties(XmlStartTag st) throws XmlPullParserException { LOG.fine("Reading new packet"); Packet pc = new Packet(); pc.setPacketNumber(packetsCnted++); return pc; } private Packet setProtoProperties(XmlStartTag st, Packet currentPacket) throws XmlPullParserException { parser.readStartTag(st); XmlAttributesHashMap<String, String> attributes = new XmlAttributesHashMap<>(); for (int i = 0; i < st.getAttributeCount(); i++) { attributes.put(st.getAttributeLocalName(i).trim(), st.getAttributeValue(i).trim()); } protoStack.push(attributes.get("name")); //tcp if ( attributes.checkIfEqual("name", "geninfo") ) { currentPacket.setPacketSize(Integer.parseInt(attributes.get("size"))); } //geninfo else if ( attributes.checkIfEqual("name", "tcp") ) { currentPacket.setTcp(true); currentPacket.setTcpPayloadStart(Integer.parseInt(attributes.get("pos")) + (Integer.parseInt(attributes.get("size")))); } else if ( attributes.checkIfEqual("name", "fake-field-wrapper") ) { currentPacket.setFragmented(true); } else if ( attributes.checkIfEqual("name", "http")){ // don't set http if this packet is the last fragment of a fragmented HTTP request/response // because we have no logic to reassemble the packet content, and anyway we want the script // to maintain the original (fragmented) nature of the traffic if (!currentPacket.isFragmented()) { currentPacket.setHttp(true); } } return currentPacket; } private Packet setFieldProperties(XmlStartTag st, Packet currentPacket) throws XmlPullParserException { parser.readStartTag(st); XmlAttributesHashMap<String, String> attributes = new XmlAttributesHashMap<>(); for (int i = 0; i < st.getAttributeCount(); i++) { attributes.put(st.getAttributeLocalName(i).trim(), st.getAttributeValue(i).trim()); } //tcp if ( attributes.checkIfEqual("name", "tcp.srcport") ) { currentPacket.setTcpSrcPort(Integer.parseInt(attributes.get("show"))); } if ( attributes.checkIfEqual("name", "tcp.dstport") ) { currentPacket.setTcpDestPort(Integer.parseInt(attributes.get("show"))); } if ( attributes.checkIfEqual("name", "tcp.len") ) { currentPacket.setTcpPayloadLength(Integer.parseInt(attributes.get("show"))); currentPacket.setTcpLen(Integer.parseInt(attributes.get("show"))); } if ( attributes.checkIfEqual("name", "tcp.flags")){ String show = attributes.get("show"); // Wireshark 1.8.5 pdml format had 0x... hex value in show. In 1.12.1 it has just the decimal value. int flags = show.startsWith("0x") ? Integer.parseInt(show.substring(2), 16) : Integer.parseInt(show); currentPacket.setTcpFlags(flags); } if ( attributes.checkIfEqual("name", "tcp.seq") ) { currentPacket.setRelativeTcpSeqNum(Integer.parseInt(attributes.get("show"))); } if ( attributes.checkIfEqual("name", "tcp.nxtseq") ) { currentPacket.setTcpNextRelativeSeqNum(Integer.parseInt(attributes.get("show"))); } if ( attributes.checkIfEqual("name", "tcp.flags.ack") ) { String value = attributes.get("value"); // Wireshark 1.99.1 pdml format had FFF... for tcp.flag value. int flagV = value.startsWith("FFF") ? 1 : Integer.parseInt(value); currentPacket.setTcpFlagsAck(1 == flagV); } if ( attributes.checkIfEqual("name", "tcp.flags.syn") ) { String value = attributes.get("value"); // Wireshark 1.99.1 pdml format had FFF... for tcp.flag value. int flagV = value.startsWith("FFF") ? 1 : Integer.parseInt(value); currentPacket.setTcpFlagsSyn(1 == flagV); } if ( attributes.checkIfEqual("name", "tcp.flags.fin") ) { String value = attributes.get("value"); // Wireshark 1.99.1 pdml format had FFF... for tcp.flag value. int flagV = value.startsWith("FFF") ? 1 : Integer.parseInt(value); currentPacket.setTcpFlagsFin(1 == flagV); } if ( attributes.checkIfEqual("name", "tcp.flags.reset") ) { String value = attributes.get("value"); // Wireshark 1.99.1 pdml format had FFF... for tcp.flag value. int flagV = value.startsWith("FFF") ? 1 : Integer.parseInt(value); currentPacket.setTcpFlagsReset(1 == flagV); } if ( attributes.checkIfEqual("name", "ip.src_host") ){ currentPacket.setIp(true); currentPacket.setSrcIpAddr(attributes.get("show")); } if ( attributes.checkIfEqual("name", "ip.dst_host") ){ currentPacket.setDestIpAddr(attributes.get("show")); } if ( attributes.checkIfEqual("name", "ipv6.host") ){ currentPacket.setIp(true); currentPacket.setSrcIpAddr(attributes.get("show")); } if ( attributes.checkIfEqual("name", "ipv6.dst") ){ currentPacket.setDestIpAddr(attributes.get("show")); } if ( attributes.checkIfEqual("name", "tcp.seq")){ currentPacket.setTcpSequenceNumber(attributes.get("value")); } if ( attributes.checkIfEqual("name", "tcp.ack")){ currentPacket.setTcpAcknowledgementNumber(attributes.get("value")); } if ( attributes.checkIfEqual("name", "tcp.stream")){ currentPacket.setTcpStream(Integer.parseInt(attributes.get("show"))); } //geninfo if ( attributes.checkIfEqual("name", "timestamp") ){ try { currentPacket.setTimeStamp(new SimpleDateFormat("MMM dd, yyyy HH:mm:ss.SSSSSSSSS", Locale.ENGLISH) .parse(attributes.get("show"))); } catch (ParseException e) { e.printStackTrace(); throw new ParserFailureException("Failed to parse simple date: " + attributes.get("show")); } currentPacket.setTimeInSecondsFromEpoch(Double.parseDouble(attributes.get("value"))); } return currentPacket; } private Packet setHttpFieldProperties(XmlStartTag st, Packet currentPacket) throws XmlPullParserException { parser.readStartTag(st); XmlAttributesHashMap<String, String> attributes = new XmlAttributesHashMap<>(); for (int i = 0; i < st.getAttributeCount(); i++) { attributes.put(st.getAttributeLocalName(i).trim(), st.getAttributeValue(i).trim()); } // used for id if ( attributes.checkIfEqual("name", "http.request.method") ) { currentPacket.setHttpRequestType(Packet.RequestType.valueOf(attributes.get("show"))); } else if ( attributes.checkIfEqual("name", "http.request.uri") ) { currentPacket.setHttpRequestURI(attributes.get("show")); } // used for formatting of payload if(fieldStack.size() == 1){ String pos = attributes.get("pos"); String size = attributes.get("size"); // Some fields don't have pos and size, e.g. data field in http proto packet if (pos != null && size != null) { currentPacket.addHttpField(attributes.get("name"), attributes.get("show"), Integer.parseInt(pos) - currentPacket.getTcpPayloadStart(), Integer.parseInt(size)); } } return currentPacket; } /** * * Miscellaneous class to do safe value comparisons regardless of nonexistent keys * * @param <K> This was designed/tested for strings only * @param <V> This was designed/tested for strings only */ private class XmlAttributesHashMap<K, V> extends HashMap<K, V> { private static final long serialVersionUID = 1L; public XmlAttributesHashMap() { super(); } public boolean checkIfEqual(K key, V value) { if ( this.containsKey(key) && this.get(key).equals(value) ) return true; return false; } } }