X-Git-Url: http://plrg.eecs.uci.edu/git/?p=pingpong.git;a=blobdiff_plain;f=Code%2FProjects%2FSmartPlugDetector%2Fsrc%2Fmain%2Fjava%2Fedu%2Fuci%2Fiotproject%2FConversation.java;h=f28b8fd2f3167aa64f91957b9ca766f804c09c0a;hp=f8ee398f588f5c8f482250cc9dad74aad35452c9;hb=eaf6818c383973cdf5fe5a7f6ee88fcd88425cbf;hpb=f9c1525915ffd641a1e20c1e9b224e57b0f5d1fc 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 f8ee398..f28b8fd 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 @@ -1,12 +1,11 @@ package edu.uci.iotproject; +import edu.uci.iotproject.util.PcapPacketUtils; import org.pcap4j.core.PcapPacket; import org.pcap4j.packet.IpV4Packet; import org.pcap4j.packet.TcpPacket; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; /** * Models a (TCP) conversation/connection/session/flow (packet's belonging to the same session between a client and a @@ -15,15 +14,51 @@ import java.util.Objects; * considered when determining equality of two {@code Conversation} instances in order to allow for a * {@code Conversation} to function as a key in data structures such as {@link java.util.Map} and {@link java.util.Set}. * See {@link #equals(Object)} for the definition of equality. + * + * @author Janus Varmarken {@literal } + * @author Rahmadi Trimananda {@literal } */ public class Conversation { + /* Begin instance properties */ + /** + * The IP of the host that is considered the client (i.e. the host that initiates the conversation) + * in this conversation. + */ private final String mClientIp; + + /** + * The port number used by the host that is considered the client in this conversation. + */ private final int mClientPort; + + /** + * The IP of the host that is considered the server (i.e. is the responder) in this conversation. + */ private final String mServerIp; + + /** + * The port number used by the server in this conversation. + */ private final int mServerPort; + + /** + * The list of packets pertaining to this conversation. + */ private final List mPackets; + /** + * Contains the sequence numbers seen so far for this {@code 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 */ + /** * Constructs a new {@code Conversation}. * @param clientIp The IP of the host that is considered the client (i.e. the host that initiates the conversation) @@ -38,46 +73,78 @@ public class Conversation { this.mServerIp = serverIp; this.mServerPort = serverPort; this.mPackets = new ArrayList<>(); + this.mSeqNumbers = new HashSet<>(); + this.mFinPackets = new ArrayList<>(); } /** * Add a packet to the list of packets associated with this conversation. * @param packet The packet that is to be added to (associated with) this conversation. + * @param ignoreRetransmissions Boolean value indicating if retransmissions should be ignored. + * If set to {@code true}, {@code packet} will not be added to the + * internal list of packets pertaining to this {@code Conversation} + * iff the sequence number of {@code packet} was already + * seen in a previous packet. */ - public void addPacket(PcapPacket packet) { - // 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)); + public void addPacket(PcapPacket packet, boolean ignoreRetransmissions) { + // 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; + int seqNo = tcpPacket.getHeader().getSequenceNumber(); + if (ignoreRetransmissions && mSeqNumbers.contains(seqNo)) { + // Packet is a retransmission. Ignore it. + return; } - 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())); - } - // ================================================================================ + // Update set of sequence numbers seen so far with sequence number of new packet. + mSeqNumbers.add(seqNo); + // Finally add packet to list of packets pertaining to this conversation. mPackets.add(packet); } + /** + * Get a list of packets pertaining to this {@code Conversation}. + * The returned list is a read-only list. + * @return the list of packets pertaining to this {@code Conversation}. + */ + public List getPackets() { + // Return read-only view to prevent external code from manipulating internal state (preserve invariant). + return Collections.unmodifiableList(mPackets); + } + + /** + * 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 if this {@code Conversation} is considered to have been gracefully shut down. + * A {@code Conversation} has been gracefully shut down if it contains a FIN+ACK pair for both directions + * (client to server, and server to client). + * @return {@code true} if the connection has been gracefully shut down, false otherwise. + */ + public boolean isGracefullyShutdown() { + // The conversation has been gracefully shut down if we have recorded a FIN from both the client and the server which have both been ack'ed. + return mFinPackets.stream().anyMatch(finAckPair -> finAckPair.isAcknowledged() && PcapPacketUtils.isSource(finAckPair.getFinPacket(), mClientIp, mClientPort)) && + mFinPackets.stream().anyMatch(finAckPair -> finAckPair.isAcknowledged() && PcapPacketUtils.isSource(finAckPair.getFinPacket(), mServerIp, mServerPort)); + } + // ========================================================================================================= // We simply reuse equals and hashCode methods of String.class to be able to use this class as a key // in a Map. @@ -104,4 +171,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