* 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 */
/**
this.mServerPort = serverPort;
this.mPackets = new ArrayList<>();
this.mSeqNumbers = new HashSet<>();
+ this.mFinPackets = new ArrayList<>();
}
/**
* 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.
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.
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
--- /dev/null
+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
--- /dev/null
+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;
+ }
+
+}