1 package edu.uci.iotproject;
3 import org.pcap4j.core.NotOpenException;
4 import org.pcap4j.core.PcapHandle;
5 import org.pcap4j.core.PcapNativeException;
6 import org.pcap4j.core.PcapPacket;
7 import org.pcap4j.packet.IpV4Packet;
8 import org.pcap4j.packet.Packet;
9 import org.pcap4j.packet.TcpPacket;
10 import org.pcap4j.packet.DnsPacket;
12 import java.io.EOFException;
13 import java.net.UnknownHostException;
14 import java.time.Instant;
16 import java.util.concurrent.TimeoutException;
19 * Provides functionality for searching for the presence of a {@link FlowPattern} in a PCAP trace.
21 * @author Janus Varmarken
23 public class FlowPatternFinder {
25 /* Class properties */
26 private final Map<Conversation, List<PcapPacket>> connections = new HashMap<>();
27 private DnsMap dnsMap;
31 public FlowPatternFinder() {
32 this.dnsMap = new DnsMap();
37 * Find pattern based on the FlowPattern object
39 * @param pcap PCAP file handler
40 * @param pattern FlowPattern class object as a comparator
42 // TODO clean up exceptions etc.
43 public void findFlowPattern(PcapHandle pcap, FlowPattern pattern)
44 throws PcapNativeException, NotOpenException, TimeoutException {
48 Set<Integer> seqNumberSet = new HashSet<Integer>();
49 while ((packet = pcap.getNextPacketEx()) != null) {
51 // Check if this is a valid DNS packet
52 dnsMap.validateAndAddNewEntry(packet);
53 // For now, we only work support pattern search in TCP over IPv4.
54 IpV4Packet ipPacket = packet.get(IpV4Packet.class);
55 TcpPacket tcpPacket = packet.get(TcpPacket.class);
56 if (ipPacket == null || tcpPacket == null) {
59 String srcAddress = ipPacket.getHeader().getSrcAddr().getHostAddress();
60 String dstAddress = ipPacket.getHeader().getDstAddr().getHostAddress();
61 int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
62 int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
63 // Is this packet related to the pattern and coming from the cloud server?
64 boolean fromServer = dnsMap.isRelatedToCloudServer(srcAddress, pattern.getHostname());
65 // Is this packet related to the pattern and going to the cloud server?
66 boolean fromClient = dnsMap.isRelatedToCloudServer(dstAddress, pattern.getHostname());
67 if (!fromServer && !fromClient) {
68 // Packet not related to pattern, skip it.
71 if (tcpPacket.getPayload() == null) {
72 // We skip non-payload control packets as these are less predictable and should therefore not be
73 // part of a signature (e.g. receiver can choose not to ACK immediately)
77 // Identify conversations (connections/sessions) by the four-tuple (clientIp, clientPort, serverIp, serverPort).
78 // TODO: this is strictly not sufficient to differentiate one TCP session from another, but should suffice for now.
79 Conversation conversation = fromClient ? new Conversation(srcAddress, srcPort, dstAddress, dstPort) :
80 new Conversation(dstAddress, dstPort, srcAddress, srcPort);
81 List<PcapPacket> listWrappedPacket = new ArrayList<>();
82 listWrappedPacket.add(packet);
83 // Create new conversation entry, or append packet to existing.
84 connections.merge(conversation, listWrappedPacket, (v1, v2) -> {
85 int seqNumber = v2.get(0).get(TcpPacket.class).getHeader().getSequenceNumber();
86 boolean retransmission = seqNumberSet.contains(seqNumber);
87 if (!retransmission) {
88 // Do not add if retransmission -> avoid duplicate packets in flow
90 seqNumberSet.add(seqNumber);
95 } catch (EOFException eofe) {
96 System.out.println("findFlowPattern: finished processing entire file");
98 } catch (UnknownHostException ex) {
100 ex.printStackTrace();
105 private void find(FlowPattern pattern) {
106 for (Conversation con : connections.keySet()) {
107 List<PcapPacket> packets = connections.get(con);
108 if (packets.size() != pattern.getPacketOrder().size()) {
109 // Not a complete match if different number of packets.
112 boolean completeMatch = true;
113 for (int i = 0; i < packets.size(); i++) {
114 TcpPacket tcpPacket = packets.get(i).get(TcpPacket.class);
115 if (tcpPacket.getPayload().length() != pattern.getPacketOrder().get(i)) {
116 completeMatch = false;
121 PcapPacket firstPacketInFlow = packets.get(0);
123 String.format("[ detected a complete match of pattern '%s' at %s]",
124 pattern.getPatternId(), firstPacketInFlow.getTimestamp().toString()));
131 * Immutable class used for identifying a conversation/connection/session/flow (packet's belonging to the same
132 * session between a client and a server).
134 private static class Conversation {
136 private final String clientIp;
137 private final int clientPort;
138 private final String serverIp;
139 private final int serverPort;
141 public Conversation(String clientIp, int clientPort, String serverIp, int serverPort) {
142 this.clientIp = clientIp;
143 this.clientPort = clientPort;
144 this.serverIp = serverIp;
145 this.serverPort = serverPort;
149 // =========================================================================================================
150 // We simply reuse equals and hashCode methods of String.class to be able to use this immutable class as a key
153 public boolean equals(Object obj) {
154 return obj instanceof Conversation && this.toString().equals(obj.toString());
157 public int hashCode() {
158 return toString().hashCode();
160 // =========================================================================================================
163 public String toString() {
164 return String.format("%s:%d %s:%d", clientIp, clientPort, serverIp, serverPort);