SignatureDetector.java: closer towards final implementation (e.g., also check packet...
authorJanus Varmarken <varmarken@gmail.com>
Sat, 22 Sep 2018 00:29:59 +0000 (17:29 -0700)
committerJanus Varmarken <varmarken@gmail.com>
Sat, 22 Sep 2018 00:34:58 +0000 (17:34 -0700)
Code/Projects/SmartPlugDetector/.idea/modules/SmartPlugDetector_main.iml
Code/Projects/SmartPlugDetector/.idea/modules/SmartPlugDetector_test.iml
Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/detection/SignatureDetector.java

index 6569157f2b61f2e5f49a8d091d8decb801657ff9..740525ed894d42070ccebfad2ab6d5fbce38e0a3 100644 (file)
@@ -5,6 +5,7 @@
     <exclude-output />
     <content url="file://$MODULE_DIR$/../../src/main">
       <sourceFolder url="file://$MODULE_DIR$/../../src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/../../src/main/resources" type="java-resource" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
index 616bd5262c42114ee35fe7eb572645777c0545fc..8d4ae84e7bb669546c9cd219c3748bd37f5964ba 100644 (file)
@@ -5,6 +5,7 @@
     <exclude-output />
     <content url="file://$MODULE_DIR$/../../src/test">
       <sourceFolder url="file://$MODULE_DIR$/../../src/test/java" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/../../src/test/resources" type="java-test-resource" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
index 54feeba6d5ae7b70ccf4357fe5bb62f9e30554f7..6cf0ba3a1995053f8a1d09333a70cf3b3b9b66e9 100644 (file)
@@ -3,11 +3,14 @@ package edu.uci.iotproject.detection;
 import edu.uci.iotproject.Conversation;
 import edu.uci.iotproject.TcpReassembler;
 import edu.uci.iotproject.analysis.TcpConversationUtils;
-import edu.uci.iotproject.util.PcapPacketUtils;
 import org.pcap4j.core.PacketListener;
 import org.pcap4j.core.PcapPacket;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
+
+import static edu.uci.iotproject.util.PcapPacketUtils.*;
 
 /**
  * TODO add class documentation.
@@ -22,16 +25,46 @@ public class SignatureDetector implements PacketListener {
      */
     private final List<List<PcapPacket>> mSignature;
 
+    /**
+     * The directions of packets in the sequence that make up {@link #mSignature}.
+     */
+    private final Conversation.Direction[] mSignatureDirections;
+
     /**
      * For reassembling the observed traffic into TCP connections.
      */
     private final TcpReassembler mTcpReassembler = new TcpReassembler();
 
-    public SignatureDetector(List<List<PcapPacket>> signature) {
+    /**
+     * IP of the router's WAN port (if analyzed traffic is captured at the ISP's point of view).
+     */
+    private final String mRouterWanIp;
+
+    public SignatureDetector(List<List<PcapPacket>> signature, String routerWanIp) {
+        Objects.requireNonNull(signature, "signature cannot be null");
+        if (signature.isEmpty() || signature.stream().anyMatch(inner -> inner.isEmpty())) {
+            throw new IllegalArgumentException("signature is empty (or contains empty inner List)");
+        }
         mSignature = signature;
+        mRouterWanIp = routerWanIp;
+        // Build the direction sequence.
+        // Note: assumes that the provided signature was captured within the local network (routerWanIp is set to null).
+        mSignatureDirections = getPacketDirections(mSignature.get(0), null);
+        /*
+         * Enforce restriction on cluster/signature members: all representatives must exhibit the same direction pattern
+         * and contain the same number of packets. Note that this is a somewhat heavy operation, so it may be disabled
+         * later on in favor of performance. However, it is only run once (at instantiation), so the overhead may be
+         * warranted in order to ensure correctness, especially during the development/debugging phase.
+         */
+        if (mSignature.stream().
+                anyMatch(inner -> !Arrays.equals(mSignatureDirections, getPacketDirections(inner, null)))) {
+            throw new IllegalArgumentException(
+                    "signature members must contain the same number of packets and exhibit the same packet direction " +
+                            "pattern"
+            );
+        }
     }
 
-
     @Override
     public void gotPacket(PcapPacket packet) {
         // Present packet to TCP reassembler so that it can be mapped to a connection (if it is a TCP packet).
@@ -45,11 +78,30 @@ public class SignatureDetector implements PacketListener {
         for (Conversation c : mTcpReassembler.getTcpConversations()) {
             for (List<PcapPacket> sequence : mSignature) {
                 boolean matchFound = isSequenceInConversation(sequence, c);
-
+                if (matchFound) {
+                    onSequenceDetected(sequence, c);
+                    // Found signature in current conversation, so break inner loop and continue with next conversation.
+                    break;
+                }
             }
+            /*
+             * TODO:
+             * if no item in cluster matches, also perform a distance-based matching to cover those cases where we did
+             * not manage to capture every single mutation of the sequence during training.
+             *
+             * Need to compute average/centroid of cluster to do so...? Compute within-cluster variance, then check if
+             * distance between input conversation and cluster average/centroid is smaller than or equal to the computed
+             * variance?
+             */
         }
     }
 
+    private void onSequenceDetected(List<PcapPacket> sequence, Conversation c) {
+        // TODO implement whatever output we want, e.g., print to std.out or notify observer objects
+
+
+    }
+
     /**
      * Examine if a {@link Conversation} contains a given sequence of packets. Note: the current implementation actually
      * searches for a substring as it does not allow for interleaved packets in {@code c} that are not in
@@ -75,13 +127,19 @@ public class SignatureDetector implements PacketListener {
         if (packets.size() < sequence.size()) {
             return false;
         }
+        /*
+         * Generate packet direction array for c. We have already generated the packet direction array for sequence as
+         * part of the constructor (mSignatureDirections).
+         */
+        Conversation.Direction[] cDirections = getPacketDirections(packets, mRouterWanIp);
         int seqIdx = 0;
         int convIdx = 0;
         while (convIdx < packets.size()) {
             PcapPacket seqPkt = sequence.get(seqIdx);
             PcapPacket convPkt = packets.get(convIdx);
-            if (convPkt.getOriginalLength() == seqPkt.getOriginalLength()) {
-                // TODO should also check direction of packets -- how to?
+            // We only have a match if packet lengths and directions match.
+            if (convPkt.getOriginalLength() == seqPkt.getOriginalLength() &&
+                    mSignatureDirections[seqIdx] == cDirections[convIdx]) {
                 // A match, advance both indices to consider next packet in sequence vs. next packet in conversation
                 seqIdx++;
                 convIdx++;
@@ -112,9 +170,39 @@ public class SignatureDetector implements PacketListener {
     private boolean isTlsSequence(List<PcapPacket> sequence) {
         // NOTE: Assumes ALL packets in sequence pertain to the same TCP connection!
         PcapPacket firstPkt = sequence.get(0);
-        int srcPort = PcapPacketUtils.getSourcePort(firstPkt);
-        int dstPort = PcapPacketUtils.getDestinationPort(firstPkt);
+        int srcPort = getSourcePort(firstPkt);
+        int dstPort = getDestinationPort(firstPkt);
         return TcpConversationUtils.isTlsPort(srcPort) || TcpConversationUtils.isTlsPort(dstPort);
     }
 
+    /**
+     * Given a {@code List<PcapPacket>}, generate a {@code Conversation.Direction[]} such that each entry in the
+     * resulting {@code Conversation.Direction[]} specifies the direction of the {@link PcapPacket} at the corresponding
+     * index in the input list.
+     * @param packets The list of packets for which to construct a corresponding array of packet directions.
+     * @param routerWanIp The IP of the router's WAN port. This is used for determining the direction of packets when
+     *                    the traffic is captured just outside the local network (at the ISP side of the router). Set to
+     *                    {@code null} if {@code packets} stem from traffic captured within the local network.
+     * @return A {@code Conversation.Direction[]} specifying the direction of the {@link PcapPacket} at the
+     *         corresponding index in {@code packets}.
+     */
+    private static Conversation.Direction[] getPacketDirections(List<PcapPacket> packets, String routerWanIp) {
+        Conversation.Direction[] directions = new Conversation.Direction[packets.size()];
+        for (int i = 0; i < packets.size(); i++) {
+            PcapPacket pkt = packets.get(i);
+            if (getSourceIp(pkt).equals(getDestinationIp(pkt))) {
+                // Sanity check: we shouldn't be processing loopback traffic
+                throw new AssertionError("loopback traffic detected");
+            }
+            if (isSrcIpLocal(pkt) || getSourceIp(pkt).equals(routerWanIp)) {
+                directions[i] = Conversation.Direction.CLIENT_TO_SERVER;
+            } else if (isDstIpLocal(pkt) || getDestinationIp(pkt).equals(routerWanIp)) {
+                directions[i] = Conversation.Direction.SERVER_TO_CLIENT;
+            } else {
+                throw new IllegalArgumentException("no local IP or router WAN port IP found, can't detect direction");
+            }
+        }
+        return directions;
+    }
+
 }