1 package edu.uci.iotproject;
3 import edu.uci.iotproject.comparison.ComparisonFunctions;
4 import edu.uci.iotproject.comparison.CompleteMatchPatternComparisonResult;
5 import edu.uci.iotproject.comparison.PatternComparisonTask;
6 import org.pcap4j.core.NotOpenException;
7 import org.pcap4j.core.PcapHandle;
8 import org.pcap4j.core.PcapNativeException;
9 import org.pcap4j.core.PcapPacket;
10 import org.pcap4j.packet.DnsPacket;
11 import org.pcap4j.packet.IpV4Packet;
12 import org.pcap4j.packet.TcpPacket;
14 import java.io.EOFException;
15 import java.net.UnknownHostException;
17 import java.util.concurrent.*;
21 * <p>Provides functionality for searching for the presence of a {@link FlowPattern} in a PCAP trace.</p>
24 * The (entire) PCAP trace is traversed and parsed on one thread (specifically, the thread that calls
25 * {@link #findFlowPattern()}). This thread builds a {@link DnsMap} using the DNS packets present in the trace and uses
26 * that {@code DnsMap} to reassemble {@link Conversation}s that <em>potentially</em> match the provided
27 * {@link FlowPattern} (in that one end/party of said conversations matches the hostname(s) specified by the given
28 * {@code FlowPattern}).
29 * These potential matches are then examined on background worker thread(s) to determine if they are indeed a (complete)
30 * match of the provided {@code FlowPattern}.
33 * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
34 * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
36 public class FlowPatternFinder {
38 /* Begin class properties */
40 * {@link ExecutorService} responsible for parallelizing pattern searches.
41 * Declared as static to allow for reuse of threads across different instances of {@code FlowPatternFinder} and to
42 * avoid the overhead of initializing a new thread pool for each {@code FlowPatternFinder} instance.
44 private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();
45 /* End class properties */
47 /* Begin instance properties */
49 * Holds a set of {@link Conversation}s that <em>potentially</em> match {@link #mPattern} since each individual
50 * {@code Conversation} is communication with the hostname identified by {@code mPattern.getHostname()}.
51 * Note that due to limitations of the {@link Set} interface (specifically, there is no {@code get(T t)} method),
52 * we have to resort to a {@link Map} (in which keys map to themselves) to "mimic" a set with {@code get(T t)}
55 * @see <a href="https://stackoverflow.com/questions/7283338/getting-an-element-from-a-set">this question on StackOverflow.com</a>
57 private final Map<Conversation, Conversation> mConversations;
59 private final DnsMap mDnsMap;
60 private final PcapHandle mPcap;
61 private final FlowPattern mPattern;
62 private final ConversationPair mConvPair;
63 private final String FILE = "./datapoints.csv";
65 private final List<Future<CompleteMatchPatternComparisonResult>> mPendingComparisons = new ArrayList<>();
66 /* End instance properties */
69 * Constructs a new {@code FlowPatternFinder}.
70 * @param pcap an <em>open</em> {@link PcapHandle} that provides access to the trace that is to be examined.
71 * @param pattern the {@link FlowPattern} to search for.
73 public FlowPatternFinder(PcapHandle pcap, FlowPattern pattern) {
74 this.mConversations = new HashMap<>();
75 this.mDnsMap = new DnsMap();
76 this.mPcap = Objects.requireNonNull(pcap,
77 String.format("Argument of type '%s' cannot be null", PcapHandle.class.getSimpleName()));
78 this.mPattern = Objects.requireNonNull(pattern,
79 String.format("Argument of type '%s' cannot be null", FlowPattern.class.getSimpleName()));
80 this.mConvPair = new ConversationPair(FILE, ConversationPair.Direction.DEVICE_TO_SERVER);
84 * Starts the pattern search.
91 * Find patterns based on the FlowPattern object (run by a thread)
93 private void findFlowPattern() {
96 // TODO: The new comparison method is pending
97 // TODO: For now, just compare using one hostname and one list per FlowPattern
98 // List<String> hostnameList = mPattern.getHostnameList();
100 while ((packet = mPcap.getNextPacketEx()) != null) {
101 // Let DnsMap handle DNS packets.
102 if (packet.get(DnsPacket.class) != null) {
103 // Check if this is a valid DNS packet
104 mDnsMap.validateAndAddNewEntry(packet);
107 // For now, we only work support pattern search in TCP over IPv4.
108 final IpV4Packet ipPacket = packet.get(IpV4Packet.class);
109 final TcpPacket tcpPacket = packet.get(TcpPacket.class);
110 if (ipPacket == null || tcpPacket == null) {
114 String srcAddress = ipPacket.getHeader().getSrcAddr().getHostAddress();
115 String dstAddress = ipPacket.getHeader().getDstAddr().getHostAddress();
116 int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
117 int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
118 // Is this packet related to the pattern; i.e. is it going to (or coming from) the cloud server?
119 boolean fromServer = mDnsMap.isRelatedToCloudServer(srcAddress, mPattern.getHostname());
120 boolean fromClient = mDnsMap.isRelatedToCloudServer(dstAddress, mPattern.getHostname());
121 // String currentHostname = hostnameList.get(hostIndex);
122 // boolean fromServer = mDnsMap.isRelatedToCloudServer(srcAddress, currentHostname);
123 // boolean fromClient = mDnsMap.isRelatedToCloudServer(dstAddress, currentHostname);
124 if (!fromServer && !fromClient) {
125 // Packet not related to pattern, skip it.
129 // Conversations (connections/sessions) are identified by the four-tuple
130 // (clientIp, clientPort, serverIp, serverPort) (see Conversation Javadoc).
131 // Create "dummy" conversation for looking up an existing entry.
132 Conversation conversation = fromClient ? new Conversation(srcAddress, srcPort, dstAddress, dstPort) :
133 new Conversation(dstAddress, dstPort, srcAddress, srcPort);
134 // Add the packet so that the "dummy" conversation can be immediately added to the map if no entry
135 // exists for the conversation that the current packet belongs to.
136 if (tcpPacket.getHeader().getFin()) {
137 // Record FIN packets.
138 conversation.addFinPacket(packet);
140 if (tcpPacket.getPayload() != null) {
141 // Record regular payload packets.
142 conversation.addPacket(packet, true);
144 // Note: does not make sense to call attemptAcknowledgementOfFin here as the new packet has no FINs
145 // in its list, so if this packet is an ACK, it would not be added anyway.
146 // Record the conversation pairs
147 if (tcpPacket.getPayload() != null) {
148 mConvPair.writeConversationPair(packet, fromClient, fromServer);
150 // Need to retain a final reference to get access to the packet in the lambda below.
151 final PcapPacket finalPacket = packet;
152 // Add the new conversation to the map if an equal entry is not already present.
153 // If an existing entry is already present, the current packet is simply added to that conversation.
154 mConversations.merge(conversation, conversation, (existingEntry, toMerge) -> {
155 // toMerge may not have any payload packets if the current packet is a FIN packet.
156 if (toMerge.getPackets().size() > 0) {
157 existingEntry.addPacket(toMerge.getPackets().get(0), true);
159 if (toMerge.getFinAckPairs().size() > 0) {
160 // Add the FIN packet to the existing entry.
161 existingEntry.addFinPacket(toMerge.getFinAckPairs().get(0).getFinPacket());
163 if (finalPacket.get(TcpPacket.class).getHeader().getAck()) {
164 existingEntry.attemptAcknowledgementOfFin(finalPacket);
166 return existingEntry;
168 // Refresh reference to point to entry in map (in case packet was added to existing entry).
169 conversation = mConversations.get(conversation);
170 if (conversation.isGracefullyShutdown()) {
171 // Conversation terminated gracefully, so we can now start analyzing it.
172 // Remove the Conversation from the map and start the analysis.
173 // Any future packets identified by the same four tuple will be tied to a new Conversation instance.
174 mConversations.remove(conversation);
175 // Create comparison task and send to executor service.
176 PatternComparisonTask<CompleteMatchPatternComparisonResult> comparisonTask =
177 new PatternComparisonTask<>(conversation, mPattern, ComparisonFunctions.SUB_SEQUENCE_COMPLETE_MATCH);
178 mPendingComparisons.add(EXECUTOR_SERVICE.submit(comparisonTask));
179 // Increment hostIndex to find the next
183 } catch (EOFException eofe) {
185 System.out.println("[ findFlowPattern ] ConversationPair writer closed!");
186 // TODO should check for leftover conversations in map here and fire tasks for those.
187 // TODO [cont'd] such tasks may be present if connections did not terminate gracefully or if there are longlived connections.
188 System.out.println("[ findFlowPattern ] Finished processing entire PCAP stream!");
189 System.out.println("[ findFlowPattern ] Now waiting for comparisons to finish...");
190 // Wait for all comparisons to finish, then output their results to std.out.
191 for(Future<CompleteMatchPatternComparisonResult> comparisonTask : mPendingComparisons) {
193 // Blocks until result is ready.
194 CompleteMatchPatternComparisonResult comparisonResult = comparisonTask.get();
195 if (comparisonResult.getResult()) {
196 System.out.println(comparisonResult.getTextualDescription());
198 } catch (InterruptedException|ExecutionException e) {
202 } catch (UnknownHostException |
203 PcapNativeException |
205 TimeoutException ex) {
206 ex.printStackTrace();