Prepare a data structure for keeping track of FIN and their corresponding ACK packets...
authorJanus Varmarken <varmarken@gmail.com>
Fri, 11 May 2018 02:22:05 +0000 (19:22 -0700)
committerJanus Varmarken <varmarken@gmail.com>
Fri, 11 May 2018 02:22:05 +0000 (19:22 -0700)
Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Conversation.java
Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/FinAckPair.java [new file with mode: 0644]
Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/util/PcapPacketUtils.java [new file with mode: 0644]

index bbf7616cd2ac2417b208f8442ac11d2d85f0bfb4..8d5780ed0b890dc9240992589b9ea57112429d9b 100644 (file)
@@ -51,6 +51,11 @@ public class Conversation {
      * Used for filtering out retransmissions.
      */
     private final Set<Integer> mSeqNumbers;
+
+    /**
+     * List of pairs FINs and their corresponding ACKs associated with this conversation.
+     */
+    private List<FinAckPair> mFinPackets;
     /* End instance properties */
 
     /**
@@ -68,6 +73,7 @@ public class Conversation {
         this.mServerPort = serverPort;
         this.mPackets = new ArrayList<>();
         this.mSeqNumbers = new HashSet<>();
+        this.mFinPackets = new ArrayList<>();
     }
 
     /**
@@ -80,36 +86,10 @@ public class Conversation {
      *                              seen in a previous packet.
      */
     public void addPacket(PcapPacket packet, boolean ignoreRetransmissions) {
-        // Apply precondition to preserve class invariant: all packets in mPackets must match the 4 tuple that
-        // defines the conversation.
-        // ==== Precondition: verify that packet does indeed pertain to conversation. ====
-        IpV4Packet ipPacket = Objects.requireNonNull(packet.get(IpV4Packet.class));
+        // Precondition: verify that packet does indeed pertain to conversation.
+        onAddPrecondition(packet);
         // For now we only support TCP flows.
         TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
-        String ipSrc = ipPacket.getHeader().getSrcAddr().getHostAddress();
-        String ipDst = ipPacket.getHeader().getDstAddr().getHostAddress();
-        int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
-        int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
-        String clientIp, serverIp;
-        int clientPort, serverPort;
-        if (ipSrc.equals(mClientIp)) {
-            clientIp = ipSrc;
-            clientPort = srcPort;
-            serverIp = ipDst;
-            serverPort = dstPort;
-        } else {
-            clientIp = ipDst;
-            clientPort = dstPort;
-            serverIp = ipSrc;
-            serverPort = srcPort;
-        }
-        if (!(clientIp.equals(mClientIp) && clientPort == mClientPort &&
-                serverIp.equals(mServerIp) && serverPort == mServerPort)) {
-            throw new IllegalArgumentException(
-                    String.format("Attempt to add packet that does not pertain to %s",
-                            Conversation.class.getSimpleName()));
-        }
-        // ================================================================================
         int seqNo = tcpPacket.getHeader().getSequenceNumber();
         if (ignoreRetransmissions && mSeqNumbers.contains(seqNo)) {
             // Packet is a retransmission. Ignore it.
@@ -121,6 +101,27 @@ public class Conversation {
         mPackets.add(packet);
     }
 
+    /**
+     * Adds a TCP FIN packet to the list of TCP FIN packets associated with this conversation.
+     * @param finPacket The TCP FIN packet that is to be added to (associated with) this conversation.
+     */
+    public void addFinPacket(PcapPacket finPacket) {
+        // Precondition: verify that packet does indeed pertain to conversation.
+        onAddPrecondition(finPacket);
+        mFinPackets.add(new FinAckPair(finPacket));
+    }
+
+    /**
+     * Attempt to ACK any FIN packets held by this conversation.
+     * @param ackPacket The ACK for a FIN previously added to this conversation.
+     */
+    public void attemptAcknowledgementOfFin(PcapPacket ackPacket) {
+        // Precondition: verify that the packet pertains to this conversation.
+        onAddPrecondition(ackPacket);
+        // Mark unack'ed FIN(s) that this ACK matches as ACK'ed (there might be more than one in case of retransmissions..?)
+        mFinPackets.replaceAll(finAckPair -> (!finAckPair.isAcknowledged() && finAckPair.isCorrespondingAckPacket(ackPacket)) ? new FinAckPair(finAckPair.getFinPacket(), ackPacket) : finAckPair);
+    }
+
     /**
      * Get a list of packets pertaining to this {@code Conversation}.
      * The returned list is a read-only list.
@@ -157,4 +158,41 @@ public class Conversation {
     public String toString() {
         return String.format("%s:%d %s:%d", mClientIp, mClientPort, mServerIp, mServerPort);
     }
+
+    /**
+     * Invoke to verify that the precondition holds when a caller attempts to add a packet to this {@code Conversation}.
+     * An {@link IllegalArgumentException} is thrown if the precondition is violated.
+     * @param packet the packet to be added to this {@code Conversation}
+     */
+    private void onAddPrecondition(PcapPacket packet) {
+        // Apply precondition to preserve class invariant: all packets in mPackets must match the 4 tuple that
+        // defines the conversation.
+        IpV4Packet ipPacket = Objects.requireNonNull(packet.get(IpV4Packet.class));
+        // For now we only support TCP flows.
+        TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
+        String ipSrc = ipPacket.getHeader().getSrcAddr().getHostAddress();
+        String ipDst = ipPacket.getHeader().getDstAddr().getHostAddress();
+        int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
+        int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
+        String clientIp, serverIp;
+        int clientPort, serverPort;
+        if (ipSrc.equals(mClientIp)) {
+            clientIp = ipSrc;
+            clientPort = srcPort;
+            serverIp = ipDst;
+            serverPort = dstPort;
+        } else {
+            clientIp = ipDst;
+            clientPort = dstPort;
+            serverIp = ipSrc;
+            serverPort = srcPort;
+        }
+        if (!(clientIp.equals(mClientIp) && clientPort == mClientPort &&
+                serverIp.equals(mServerIp) && serverPort == mServerPort)) {
+            throw new IllegalArgumentException(
+                    String.format("Attempt to add packet that does not pertain to %s",
+                            Conversation.class.getSimpleName()));
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/FinAckPair.java b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/FinAckPair.java
new file mode 100644 (file)
index 0000000..fccc97a
--- /dev/null
@@ -0,0 +1,148 @@
+package edu.uci.iotproject;
+
+import org.pcap4j.core.PcapPacket;
+import org.pcap4j.packet.IpV4Packet;
+import org.pcap4j.packet.TcpPacket;
+
+/**
+ * Groups a FIN packet and its corresponding ACK packet. <b>Immutable and thread safe</b>.
+ *
+ * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
+ * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
+ */
+public class FinAckPair {
+
+    private final PcapPacket mFinPacket;
+    private final PcapPacket mCorrespondingAckPacket;
+
+    /**
+     * Constructs a {@code FinAckPair} given a FIN packet.
+     * The corresponding ACK packet field is set to {@code null}.
+     * @param finPacket A FIN packet.
+     */
+    public FinAckPair(PcapPacket finPacket) {
+        if (!finPacket.get(TcpPacket.class).getHeader().getFin()) {
+            throw new IllegalArgumentException("not a FIN packet");
+        }
+        mFinPacket = finPacket;
+        mCorrespondingAckPacket = null;
+    }
+
+    /**
+     * Constructs a {@code FinAckPair} given a FIN and an ACK packet.
+     * @param finPacket A FIN packet.
+     * @param correspondingAckPacket The ACK packet corresponding to {@code finPacket}.
+     */
+    public FinAckPair(PcapPacket finPacket, PcapPacket correspondingAckPacket) {
+        // Enforce class invariant, i.e. that the FIN and ACK are related.
+        // Note that it is indirectly checked whether finPacket is indeed a FIN packet
+        // as isCorrespondingAckPacket calls the single parameter constructor.
+        if (!FinAckPair.isCorrespondingAckPacket(finPacket, correspondingAckPacket)) {
+            throw new IllegalArgumentException("FIN and ACK not related");
+        }
+        mFinPacket = finPacket;
+        mCorrespondingAckPacket = correspondingAckPacket;
+    }
+
+    /**
+     * Get the FIN packet of this pair.
+     * @return the FIN packet of this pair.
+     */
+    public PcapPacket getFinPacket() {
+        return mFinPacket;
+    }
+
+    /**
+     * Get the corresponding ACK packet of this pair, if any.
+     * @return the corresponding ACK packet of this pair, if any.
+     */
+    public PcapPacket getCorrespondingAckPacket() {
+        return mCorrespondingAckPacket;
+    }
+
+    /**
+     * Was the FIN in this {@code FinAckPair} acknowledged?
+     *
+     * @return {@code true} if the corresponding ACK has been set in this {@code FinAckPair}.
+     */
+    public boolean isAcknowledged() {
+        return mFinPacket != null && mCorrespondingAckPacket != null;
+    }
+
+    /**
+     * Checks if a given packet is an ACK corresponding to the FIN packet in this {@code FinAckPair}.
+     * @return {@code true} if {@code packet} is an ACK that corresponds to the FIN in this pair, {@code false} otherwise.
+     */
+    public boolean isCorrespondingAckPacket(PcapPacket packet) {
+        IpV4Packet inputIpPacket = packet.get(IpV4Packet.class);
+        TcpPacket inputTcpPacket = packet.get(TcpPacket.class);
+        if (inputIpPacket == null || inputTcpPacket == null || !inputTcpPacket.getHeader().getAck()) {
+            return false;
+        }
+
+        IpV4Packet finIpPacket = mFinPacket.get(IpV4Packet.class);
+        TcpPacket finTcpPacket = mFinPacket.get(TcpPacket.class);
+
+        // Extract (srcIp:port,dstIp:port) for input and member (FIN) packets.
+        String inputPacketIpSrc = inputIpPacket.getHeader().getSrcAddr().getHostAddress();
+        String inputPacketIpDst = inputIpPacket.getHeader().getDstAddr().getHostAddress();
+        int inputPacketPortSrc = inputTcpPacket.getHeader().getSrcPort().valueAsInt();
+        int inputPacketPortDst = inputTcpPacket.getHeader().getDstPort().valueAsInt();
+        String finPacketIpSrc = finIpPacket.getHeader().getSrcAddr().getHostAddress();
+        String finPacketIpDst = finIpPacket.getHeader().getDstAddr().getHostAddress();
+        int finPacketPortSrc = finTcpPacket.getHeader().getSrcPort().valueAsInt();
+        int finPacketPortDst = finTcpPacket.getHeader().getDstPort().valueAsInt();
+
+        // For the two packets to be related, the dst of one must be the src of the other.
+        // Split into multiple if statements for readability. First check IP fields, then ports.
+        if (!(inputPacketIpDst.equals(finPacketIpSrc) && finPacketIpDst.equals(inputPacketIpSrc))) {
+            return false;
+        }
+        if (!(inputPacketPortDst == finPacketPortSrc && finPacketPortDst == inputPacketPortSrc)) {
+            return false;
+        }
+
+        // Packets are (most likely) related (part of same conversation/stream).
+        // Now all that is left for us to check is if the sequence numbers match.
+        // Note: recall that the FIN packet advances the seq numbers by 1,
+        // so the ACK number will be one larger than the seq. number in the FIN packet.
+        return inputTcpPacket.getHeader().getAcknowledgmentNumber() == finTcpPacket.getHeader().getSequenceNumber() + 1;
+    }
+
+    /**
+     * Static method to check if two given packets are a FIN and the corresponding ACK packet.
+     * The purpose of this method is a workaround to enforce the class invariant in the two parameter constructor.
+     * Specifically, the following should be avoided:
+     * <pre>
+     *     public FinAckPair(PcapPacket finPacket, PcapPacket correspondingAckPacket) {
+     *         mFinPacket = finPacket;
+     *         // Below line is considered back practice as the object has not been fully initialized at this stage.
+     *         if (!this.isCorrespondingAckPacket(correspondingAckPacket)) {
+     *             // ... throw exception
+     *         }
+     *     }
+     * </pre>
+     * @param finPacket The FIN packet.
+     * @param ackPacket The ACK packet that is to be checked if it corresponds to the given FIN packet.
+     * @return {@code true} if the ACK corresponds to the FIN, {@code false} otherwise.
+     */
+    private static boolean isCorrespondingAckPacket(PcapPacket finPacket, PcapPacket ackPacket) {
+        FinAckPair tmp = new FinAckPair(finPacket);
+        return tmp.isCorrespondingAckPacket(ackPacket);
+    }
+
+}
+
+//    /**
+//     * Sets the corresponding ACK packet in this {@code FinAckPair}.
+//     * The method internally verifies if the given {@code packet} does indeed correspond to the FIN packet in this pair.
+//     * @param packet The packet that is an ACK of the FIN in this pair.
+//     * @return {@code true} if the packet was successfully set, {@code false} otherwise (the packet did not correspond to the FIN packet in this pair).
+//     */
+//    public synchronized boolean setCorrespondingAckPacket(PcapPacket packet) {
+//        if (isCorrespondingAckPacket(packet)) {
+//            mCorrespondingAckPacket = packet;
+//            return true;
+//        }
+//        return false;
+//    }
\ No newline at end of file
diff --git a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/util/PcapPacketUtils.java b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/util/PcapPacketUtils.java
new file mode 100644 (file)
index 0000000..67421b8
--- /dev/null
@@ -0,0 +1,33 @@
+package edu.uci.iotproject.util;
+
+import org.pcap4j.core.PcapPacket;
+import org.pcap4j.packet.IpV4Packet;
+import org.pcap4j.packet.TcpPacket;
+
+import java.util.Objects;
+
+/**
+ * Utility methods for inspecting {@link PcapPacket} properties. Currently not used.
+ *
+ * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
+ * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
+ */
+public final class PcapPacketUtils {
+
+    /**
+     * Helper method to determine if the given combination of IP and port matches the source of the given packet.
+     * @param packet The packet to check.
+     * @param ip The IP to look for in the ip.src field of {@code packet}.
+     * @param port The port to look for in the tcp.port field of {@code packet}.
+     * @return {@code true} if the given ip+port match the corresponding fields in {@code packet}.
+     */
+    public static boolean isSource(PcapPacket packet, String ip, int port) {
+        IpV4Packet ipPacket = Objects.requireNonNull(packet.get(IpV4Packet.class));
+        // For now we only support TCP flows.
+        TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
+        String ipSrc = ipPacket.getHeader().getSrcAddr().getHostAddress();
+        int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
+        return ipSrc.equals(ip) && srcPort == port;
+    }
+
+}