/**
* List of SYN packets pertaining to this conversation.
*/
- private List<PcapPacket> mSynPackets;
+ private final List<PcapPacket> mSynPackets;
/**
* List of pairs FINs and their corresponding ACKs associated with this conversation.
*/
- private List<FinAckPair> mFinPackets;
+ private final List<FinAckPair> mFinPackets;
+
+ /**
+ * List of RST packets associated with this conversation.
+ */
+ private final List<PcapPacket> mRstPackets;
/* End instance properties */
/**
this.mSeqNumbersSrv = new HashSet<>();
this.mSynPackets = new ArrayList<>();
this.mFinPackets = new ArrayList<>();
+ this.mRstPackets = new ArrayList<>();
}
/**
mFinPackets.stream().anyMatch(finAckPair -> finAckPair.isAcknowledged() && PcapPacketUtils.isSource(finAckPair.getFinPacket(), mServerIp, mServerPort));
}
+ /**
+ * Add a TCP segment for which the RST flag is set to this {@code Conversation}.
+ * @param packet A {@link PcapPacket} wrapping a TCP segment pertaining to this {@code Conversation} for which the
+ * RST flag is set.
+ */
+ public void addRstPacket(PcapPacket packet) {
+ /*
+ * TODO:
+ * When now also keeping track of RST packets, should we also...?
+ * 1) Prevent later packets from being added once a RST segment has been added?
+ * 2) Extend 'isGracefullyShutdown()' to also consider RST segments, or add another method, 'isShutdown()' that
+ * both considers FIN/ACK (graceful) as well as RST (abrupt/"ungraceful") shutdown?
+ * 3) Should it be impossible to associate more than one RST segment with each Conversation?
+ */
+ onAddPrecondition(packet);
+ TcpPacket tcpPacket = packet.get(TcpPacket.class);
+ if (tcpPacket == null || !tcpPacket.getHeader().getRst()) {
+ throw new IllegalArgumentException("not a RST packet");
+ }
+ mRstPackets.add(packet);
+ }
+
+ /**
+ * Get the TCP segments pertaining to this {@code Conversation} for which it was detected that the RST flag is set.
+ * @return the TCP segments pertaining to this {@code Conversation} for which it was detected that the RST flag is
+ * set.
+ */
+ public List<PcapPacket> getRstPackets() {
+ return Collections.unmodifiableList(mRstPackets);
+ }
+
// =========================================================================================================
// We simply reuse equals and hashCode methods of String.class to be able to use this class as a key
// in a Map.
case SERVER_TO_CLIENT:
return mSeqNumbersSrv.contains(seqNo);
default:
- throw new RuntimeException(String.format("Unexpected value of enum '%s'",
+ throw new AssertionError(String.format("Unexpected value of enum '%s'",
Direction.class.getSimpleName()));
}
}
mSeqNumbersSrv.add(seqNo);
break;
default:
- throw new RuntimeException(String.format("Unexpected value of enum '%s'",
+ throw new AssertionError(String.format("Unexpected value of enum '%s'",
Direction.class.getSimpleName()));
}
}
/**
- * Determine the direction of {@code packet}.
+ * Determine the direction of {@code packet}. An {@link IllegalArgumentException} is thrown if {@code packet} does
+ * not pertain to this conversation.
+ *
* @param packet The packet whose direction is to be determined.
* @return A {@link Direction} indicating the direction of the packet.
*/
- private Direction getDirection(PcapPacket packet) {
+ public Direction getDirection(PcapPacket packet) {
IpV4Packet ipPacket = packet.get(IpV4Packet.class);
String ipSrc = ipPacket.getHeader().getSrcAddr().getHostAddress();
String ipDst = ipPacket.getHeader().getDstAddr().getHostAddress();
/**
* Utility enum for expressing the direction of a packet pertaining to this {@code Conversation}.
*/
- private enum Direction {
- CLIENT_TO_SERVER, SERVER_TO_CLIENT
+ public enum Direction {
+
+ CLIENT_TO_SERVER {
+ @Override
+ public String toCompactString() {
+ return "C->S";
+ }
+ },
+ SERVER_TO_CLIENT {
+ @Override
+ public String toCompactString() {
+ return "S->C";
+ }
+ };
+
+
+ /**
+ * Get a compact string representation of this {@code Direction}.
+ * @return a compact string representation of this {@code Direction}.
+ */
+ abstract public String toCompactString();
+
}
-}
\ No newline at end of file
+}
// final String inputPcapFile = path + "/2018-07/tplink/tplink.wlan1.local.pcap";
// final String outputPcapFile = path + "/2018-07/tplink/tplink-processed.pcap";
// final String triggerTimesFile = path + "/2018-07/tplink/tplink-july-25-2018.timestamps";
+// final String deviceIp = "192.168.1.159";
+
+ // 2b) TP-Link July 25 experiment TRUNCATED:
+ // Only contains "true local" events, i.e., before the behavior changes to remote-like behavior.
+ // Last included event is at July 25 10:38:11; file filtered to only include packets with arrival time <= 10:38:27.
+// final String inputPcapFile = path + "/2018-07/tplink/tplink.wlan1.local.truncated.pcap";
+// final String outputPcapFile = path + "/2018-07/tplink/tplink-processed.truncated.pcap";
+// final String triggerTimesFile = path + "/2018-07/tplink/tplink-july-25-2018.truncated.timestamps";
// final String deviceIp = "192.168.1.159";
// 3) SmartThings Plug July 25 experiment
--- /dev/null
+package edu.uci.iotproject;
+
+import edu.uci.iotproject.comparison.seqalignment.AlignmentPricer;
+import edu.uci.iotproject.comparison.seqalignment.SequenceAlignment;
+import org.pcap4j.core.PcapPacket;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * TODO add class documentation.
+ *
+ * @author Janus Varmarken
+ */
+public class SequenceExtraction {
+
+
+ private final SequenceAlignment<Integer> mAlignmentAlg;
+
+
+ public SequenceExtraction() {
+ mAlignmentAlg = new SequenceAlignment<>(new AlignmentPricer<>((i1,i2) -> Math.abs(i1-i2), i -> 10));
+ }
+
+
+ public SequenceExtraction(SequenceAlignment<Integer> alignmentAlgorithm) {
+ mAlignmentAlg = alignmentAlgorithm;
+ }
+
+ // Initial
+// /**
+// *
+// * @param convsForAction A set of {@link Conversation}s known to be associated with a single type of user action.
+// */
+// public void extract(List<Conversation> convsForAction) {
+// int maxDifference = 0;
+//
+// for (int i = 0; i < convsForAction.size(); i++) {
+// for (int j = i+1; j < convsForAction.size(); i++) {
+// Integer[] sequence1 = getPacketLengthSequence(convsForAction.get(i));
+// Integer[] sequence2 = getPacketLengthSequence(convsForAction.get(j));
+// int alignmentCost = mAlignmentAlg.calculateAlignment(sequence1, sequence2);
+// if (alignmentCost > maxDifference) {
+// maxDifference = alignmentCost;
+// }
+// }
+// }
+//
+// }
+
+
+// public void extract(Map<String, List<Conversation>> hostnameToConvs) {
+// int maxDifference = 0;
+//
+// for (int i = 0; i < convsForAction.size(); i++) {
+// for (int j = i+1; j < convsForAction.size(); i++) {
+// Integer[] sequence1 = getPacketLengthSequence(convsForAction.get(i));
+// Integer[] sequence2 = getPacketLengthSequence(convsForAction.get(j));
+// int alignmentCost = mAlignmentAlg.calculateAlignment(sequence1, sequence2);
+// if (alignmentCost > maxDifference) {
+// maxDifference = alignmentCost;
+// }
+// }
+// }
+//
+// }
+
+ private Integer[] getPacketLengthSequence(Conversation c) {
+ List<PcapPacket> packets = c.getPackets();
+ Integer[] packetLengthSequence = new Integer[packets.size()];
+ for (int i = 0; i < packetLengthSequence.length; i++) {
+ packetLengthSequence[i] = packets.get(i).length();
+ }
+ return packetLengthSequence;
+ }
+}
import org.pcap4j.core.PacketListener;
import org.pcap4j.core.PcapPacket;
+import org.pcap4j.packet.IpV4Packet;
import org.pcap4j.packet.TcpPacket;
import java.util.*;
if (!conv.isRetransmission(srvSynPacket) && !conv.addSynPacket(srvSynPacket)) {
// For safety/debugging: if NOT a retransmission and add fails,
// something has gone terribly wrong/invariant is broken.
- throw new IllegalStateException("Attempt to add SYN ACK packet that was NOT a retransmission failed." +
+ throw new AssertionError("Attempt to add SYN ACK packet that was NOT a retransmission failed." +
Conversation.class.getSimpleName() + " invariant broken.");
}
}
private void processRstPacket(PcapPacket rstPacket) {
Conversation conv = getOngoingConversationOrCreateNew(rstPacket);
+ // Add RST packet to conversation.
+ conv.addRstPacket(rstPacket);
// Move conversation to set of terminated conversations.
mTerminatedConversations.add(conv);
mOpenConversations.remove(conv, conv);
conv = Conversation.fromPcapPacket(pcapPacket, false);
} else {
// TODO: can we do anything else but arbitrarily select who is designated as the server in this case?
- conv = Conversation.fromPcapPacket(pcapPacket, false);
+ // We can check if the IP prefix matches a local IP when handling traffic observed inside the local
+ // network, but that obviously won't be a useful strategy for an observer at the WAN port.
+ String srcIp = pcapPacket.get(IpV4Packet.class).getHeader().getSrcAddr().getHostAddress();
+ boolean clientIsSrc = srcIp.startsWith("10.0.1.") || srcIp.startsWith("192.168.1.");
+ conv = Conversation.fromPcapPacket(pcapPacket, clientIsSrc);
}
mOpenConversations.put(conv, conv);
}
import edu.uci.iotproject.Conversation;
import edu.uci.iotproject.DnsMap;
+import edu.uci.iotproject.FinAckPair;
import edu.uci.iotproject.util.PcapPacketUtils;
import org.pcap4j.core.PcapPacket;
import org.pcap4j.packet.IpV4Packet;
*/
public class TcpConversationUtils {
-
/**
* <p>
* Given a {@link Conversation}, extract its set of "packet pairs", i.e., pairs of request-reply packets.
continue;
}
StringBuilder sb = new StringBuilder();
- for (PcapPacket pp : conv.getPackets()) {
- if (sb.length() != 0) {
- // only add a space if there's preceding content
- sb.append(" ");
+ // Add SYN and SYNACK at front of sequence to indicate if we saw the handshake or if recording started in
+ // the middle of the conversation.
+ for (PcapPacket syn : conv.getSynPackets()) {
+ TcpPacket.TcpHeader tcpHeader = syn.get(TcpPacket.class).getHeader();
+ if (tcpHeader.getSyn() && tcpHeader.getAck()) {
+ // Only append a space if there's preceding content.
+ appendSpaceIfNotEmpty(sb);
+ sb.append("SYNACK");
+ } else if (tcpHeader.getSyn()) {
+ if (sb.length() != 0) {
+ // If present in the trace, the client's SYN should be at the front of the list, so it should be
+ // appended as the first item.
+ throw new AssertionError("StringBuilder had content when appending SYN");
+ }
+ sb.append("SYN");
}
- sb.append(pp.length());
+ }
+ // Then append the length of all application data packets.
+ for (PcapPacket pp : conv.getPackets()) {
+ // Only append a space if there's preceding content.
+ appendSpaceIfNotEmpty(sb);
+ sb.append("(" + conv.getDirection(pp).toCompactString() + "_" + pp.length() + ")");
+ }
+ // Then append the logged FINs to indicate if conversation was terminated gracefully.
+ for (FinAckPair fap : conv.getFinAckPairs()) {
+ appendSpaceIfNotEmpty(sb);
+ sb.append(fap.isAcknowledged() ? "FINACK" : "FIN");
+ }
+ // Then append the logged RSTs to indicate if conversation was terminated abruptly.
+ for (PcapPacket pp : conv.getRstPackets()) {
+ appendSpaceIfNotEmpty(sb);
+ sb.append("RST");
}
List<Conversation> oneItemList = new ArrayList<>();
oneItemList.add(conv);
}
return result;
}
+
+ /**
+ * Appends a space to {@code sb} <em>iff</em> {@code sb} already contains some content.
+ * @param sb A {@link StringBuilder} that should have a space appended <em>iff</em> it is not empty.
+ */
+ private static void appendSpaceIfNotEmpty(StringBuilder sb) {
+ if (sb.length() != 0) {
+ sb.append(" ");
+ }
+ }
}