From: Janus Varmarken Date: Fri, 11 May 2018 02:22:05 +0000 (-0700) Subject: Prepare a data structure for keeping track of FIN and their corresponding ACK packets... X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=9c41e9509bc2781359f70c07939b617c9e7e6b68;p=pingpong.git Prepare a data structure for keeping track of FIN and their corresponding ACK packets. This is to be used for detecting when a connection is (gracefully) shut down. --- diff --git a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Conversation.java b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Conversation.java index bbf7616..8d5780e 100644 --- a/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Conversation.java +++ b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/Conversation.java @@ -51,6 +51,11 @@ public class Conversation { * Used for filtering out retransmissions. */ private final Set mSeqNumbers; + + /** + * List of pairs FINs and their corresponding ACKs associated with this conversation. + */ + private List 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 index 0000000..fccc97a --- /dev/null +++ b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/FinAckPair.java @@ -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. Immutable and thread safe. + * + * @author Janus Varmarken {@literal } + * @author Rahmadi Trimananda {@literal } + */ +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: + *
+     *     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
+     *         }
+     *     }
+     * 
+ * @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 index 0000000..67421b8 --- /dev/null +++ b/Code/Projects/SmartPlugDetector/src/main/java/edu/uci/iotproject/util/PcapPacketUtils.java @@ -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 } + * @author Rahmadi Trimananda {@literal } + */ +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; + } + +}