Adding feature to hold multiple hostnames and lists of packet orders in FlowPattern...
[pingpong.git] / Code / Projects / SmartPlugDetector / src / main / java / edu / uci / iotproject / FlowPatternFinder.java
1 package edu.uci.iotproject;
2
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;
13
14 import java.io.EOFException;
15 import java.net.UnknownHostException;
16 import java.util.*;
17 import java.util.concurrent.*;
18
19
20 /**
21  * <p>Provides functionality for searching for the presence of a {@link FlowPattern} in a PCAP trace.</p>
22  *
23  * <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  * matches of the provided {@code FlowPattern}.
31  * </p>
32  *
33  * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
34  * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
35  */
36 public class FlowPatternFinder {
37
38     /* Begin class properties */
39     /**
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.
43      */
44     private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();
45     /* End class properties */
46
47     /* Begin instance properties */
48     /**
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)}
53      * functionality.
54      *
55      * @see <a href="https://stackoverflow.com/questions/7283338/getting-an-element-from-a-set">this question on StackOverflow.com</a>
56      */
57     private final Map<Conversation, Conversation> mConversations;
58
59     private final DnsMap mDnsMap;
60     private final PcapHandle mPcap;
61     private final FlowPattern mPattern;
62
63     private final List<Future<CompleteMatchPatternComparisonResult>> mPendingComparisons = new ArrayList<>();
64     /* End instance properties */
65
66     /**
67      * Constructs a new {@code FlowPatternFinder}.
68      * @param pcap an <em>open</em> {@link PcapHandle} that provides access to the trace that is to be examined.
69      * @param pattern the {@link FlowPattern} to search for.
70      */
71     public FlowPatternFinder(PcapHandle pcap, FlowPattern pattern) {
72         this.mConversations = new HashMap<>();
73         this.mDnsMap = new DnsMap();
74         this.mPcap = Objects.requireNonNull(pcap,
75                 String.format("Argument of type '%s' cannot be null", PcapHandle.class.getSimpleName()));
76         this.mPattern = Objects.requireNonNull(pattern,
77                 String.format("Argument of type '%s' cannot be null", FlowPattern.class.getSimpleName()));
78     }
79
80     /**
81      * Starts the pattern search.
82      */
83     public void start() {
84         findFlowPattern();
85     }
86
87     /**
88      * Find patterns based on the FlowPattern object (run by a thread)
89      */
90     private void findFlowPattern() {
91         try {
92             PcapPacket packet;
93 //            TODO: The new comparison method is pending
94 //            TODO: For now, just compare using one hostname and one list per FlowPattern
95 //            List<String> hostnameList = mPattern.getHostnameList();
96 //            int hostIndex = 0;
97             int patternLength = mPattern.getLength();
98             while ((packet = mPcap.getNextPacketEx()) != null) {
99                 // Let DnsMap handle DNS packets.
100                 if (packet.get(DnsPacket.class) != null) {
101                     // Check if this is a valid DNS packet
102                     mDnsMap.validateAndAddNewEntry(packet);
103                     continue;
104                 }
105                 // For now, we only work support pattern search in TCP over IPv4.
106                 IpV4Packet ipPacket = packet.get(IpV4Packet.class);
107                 TcpPacket tcpPacket = packet.get(TcpPacket.class);
108                 if (ipPacket == null || tcpPacket == null) {
109                     continue;
110                 }
111                 if (tcpPacket.getPayload() == null) {
112                     // We skip non-payload control packets as these are less predictable
113                     continue;
114                 }
115                 String srcAddress = ipPacket.getHeader().getSrcAddr().getHostAddress();
116                 String dstAddress = ipPacket.getHeader().getDstAddr().getHostAddress();
117                 int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
118                 int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
119                 // Is this packet related to the pattern; i.e. is it going to (or coming from) the cloud server?
120                 boolean fromServer = mDnsMap.isRelatedToCloudServer(srcAddress, mPattern.getHostname());
121                 boolean fromClient = mDnsMap.isRelatedToCloudServer(dstAddress, mPattern.getHostname());
122 //                String currentHostname = hostnameList.get(hostIndex);
123 //                boolean fromServer = mDnsMap.isRelatedToCloudServer(srcAddress, currentHostname);
124 //                boolean fromClient = mDnsMap.isRelatedToCloudServer(dstAddress, currentHostname);
125                 if (!fromServer && !fromClient) {
126                     // Packet not related to pattern, skip it.
127                     continue;
128                 }
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                 conversation.addPacket(packet, true);
137                 // Add the new conversation to the map if an equal entry is not already present.
138                 // If an existing entry is already present, the current packet is simply added to that conversation.
139                 mConversations.merge(conversation, conversation, (existingEntry, toMerge) -> {
140                     // toMerge only has a single packet, which is the same as referred to by 'packet' variable, but need
141                     // this hack as 'packet' is not final and hence cannot be referred to in a lambda.
142                     existingEntry.addPacket(toMerge.getPackets().get(0), true);
143                     return existingEntry;
144                 });
145                 // Refresh reference to point to entry in map (in case packet was added to existing entry).
146                 conversation = mConversations.get(conversation);
147                 if (conversation.getPackets().size() == mPattern.getLength()) {
148 //                if (conversation.getPackets().size() == mPattern.getLength(currentHostname)) {
149                     // Conversation reached a size that matches the expected size.
150                     // Remove the Conversation from the map and start the analysis.
151                     // Any future packets identified by the same four tuple will be tied to a new Conversation instance.
152                     // This might, for example, occur if the same conversation is reused for multiple events.
153                     mConversations.remove(conversation);
154                     // Create comparison task and send to executor service.
155                     PatternComparisonTask<CompleteMatchPatternComparisonResult> comparisonTask =
156                             new PatternComparisonTask<>(conversation, mPattern, ComparisonFunctions.COMPLETE_MATCH);
157                     mPendingComparisons.add(EXECUTOR_SERVICE.submit(comparisonTask));
158                     // Increment hostIndex to find the next
159                     
160                 }
161             }
162         } catch (EOFException eofe) {
163             System.out.println("[ findFlowPattern ] Finished processing entire PCAP stream!");
164             System.out.println("[ findFlowPattern ] Now waiting for comparisons to finish...");
165             // Wait for all comparisons to finish, then output their results to std.out.
166             for(Future<CompleteMatchPatternComparisonResult> comparisonTask : mPendingComparisons) {
167                 try {
168                     // Blocks until result is ready.
169                     CompleteMatchPatternComparisonResult comparisonResult = comparisonTask.get();
170                     if (comparisonResult.getResult()) {
171                         System.out.println(comparisonResult.getTextualDescription());
172                     }
173                 } catch (InterruptedException|ExecutionException e) {
174                     e.printStackTrace();
175                 }
176             }
177         } catch (UnknownHostException |
178                  PcapNativeException  |
179                  NotOpenException     |
180                  TimeoutException ex) {
181             ex.printStackTrace();
182         }
183     }
184
185 }