1 package edu.uci.iotproject.detection;
3 import edu.uci.iotproject.Layer2Flow;
4 import edu.uci.iotproject.L2FlowReassembler;
5 import edu.uci.iotproject.StateMachine;
6 import edu.uci.iotproject.util.PcapPacketUtils;
7 import org.pcap4j.core.PacketListener;
8 import org.pcap4j.core.PcapPacket;
9 import org.pcap4j.util.MacAddress;
14 * Layer 2 cluster matcher.
16 * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
17 * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
19 public class L2ClusterMatcher extends AbstractClusterMatcher implements PacketListener {
21 private final MacAddress mRouterMac = null;
22 private final MacAddress mPhoneMac = null;
23 private final MacAddress mDeviceMac = null;
26 * Reassembles traffic flows.
28 private final L2FlowReassembler mFlowReassembler = new L2FlowReassembler();
31 * Each inner set holds the possible packet lengths for the packet at the corresponding index in a sequemce, taken
32 * across all sequences in {@link #mCluster}. For example, if the cluster is comprised of the sequences [112, 115]
33 * and [112, 116], the set at index 0 will be {112}, and the set at index 1 will be {115, 116}.
35 private final List<Set<Integer>> mValidPktLengths;
39 // Maintain one state machine for each layer...?
40 private final StateMachine[] seqMatchers;
42 public L2ClusterMatcher(List<List<PcapPacket>> cluster) {
45 mValidPktLengths = new ArrayList<>();
46 for (int i = 0; i < mCluster.get(0).size(); i++) {
47 mValidPktLengths.add(new HashSet<>());
49 for (List<PcapPacket> seqVariation : mCluster) {
50 for (int i = 0; i < seqVariation.size(); i++) {
51 mValidPktLengths.get(i).add(seqVariation.get(i).getOriginalLength());
55 seqMatchers = new StateMachine[mValidPktLengths.size()];
59 protected List<List<PcapPacket>> pruneCluster(List<List<PcapPacket>> cluster) {
65 public void gotPacket(PcapPacket packet) {
66 for (int i = 0; i < seqMatchers.length; i++) {
67 StateMachine sm = seqMatchers[i];
68 if (sm.attemptAdvance(packet)) {
80 for (int i = 0; i < mValidPktLengths.size(); i++) {
81 if (mValidPktLengths.get(i).contains(packet.getOriginalLength())) {
82 // This packet length is potentially of interest to state machines that currently expect the i'th packet
83 // of the searched sequence
91 // Forward to flow reassembler
92 mFlowReassembler.gotPacket(packet);
100 public void performDetection() {
101 for (Layer2Flow flow : mFlowReassembler.getFlows()) {
102 List<PcapPacket> flowPkts = flow.getPackets();
104 for (List<PcapPacket> signatureSequence : mCluster) {
111 private Optional<List<PcapPacket>> findSubsequenceInSequence(List<PcapPacket> subsequence,
112 List<PcapPacket> sequence,
113 boolean[] subsequenceDirections) {
114 if (sequence.size() < subsequence.size()) {
115 // If subsequence is longer, it cannot be contained in sequence.
116 return Optional.empty();
118 // If packet directions have not been precomputed by calling code, we need to construct them.
119 if (subsequenceDirections == null) {
120 subsequenceDirections = getPacketDirections(subsequence);
128 // if (sequenceDirections == null) {
129 // sequenceDirections = getPacketDirections(sequence);
133 boolean[] sequenceDirections;
137 while (seqIdx < sequence.size()) {
138 if (subseqIdx == 0) {
139 // Every time we (re-)start matching (i.e., when we consider the first element of subsequence), we must
140 // recompute the directions array for the subsequence.size() next elements of sequence so that we can
141 // perform index-wise comparisons of the individual elements of the two direction arrays. If we compute
142 // the directions array for the entire sequence in one go, we may end up with a reversed representation
143 // of the packet directions (i.e. one in which all boolean values in the array are flipped to be the
144 // opposite of what is the expected order) for a subsection of sequence that actually obeys the expected
145 // directions (as defined by the directions array corresponding to subsequence), depending on the packets
146 // that come earlier (as we always use 'true' for the first packet direction of a sequence).
147 int toIndex = Integer.min(seqIdx + subsequence.size(), sequence.size());
148 sequenceDirections = getPacketDirections(sequence.subList(seqIdx, toIndex));
152 PcapPacket subseqPkt = subsequence.get(subseqIdx);
153 PcapPacket seqPkt = sequence.get(seqIdx);
154 // We only have a match if packet lengths and directions match.
155 if (subseqPkt.getOriginalLength() == seqPkt.getOriginalLength() &&
156 subsequenceDirections[subseqIdx] == sequenceDirections[subseqIdx]) {
166 * Returns a boolean array {@code b} such that each entry in {@code b} indicates the direction of the packet at the
167 * corresponding index in {@code pktSequence}. As there is no notion of client and server, we model the
168 * packet directions as simple binary values. The direction of the first packet in {@code pktSequence} (and all
169 * subsequent packets going in the same direction) is denoted using a value of {@code true}, and all packets going
170 * in the opposite direction are denoted using a value of {@code false}.
172 * @param pktSequence A sequence of packets exchanged between two hosts for which packet directions are to be
174 * @return The packet directions for {@code pktSequence}.
176 private boolean[] getPacketDirections(List<PcapPacket> pktSequence) {
177 boolean[] directions = new boolean[pktSequence.size()];
178 for (int i = 0; i < pktSequence.size(); i++) {
180 // Special case for first packet: no previous packet to compare against.
181 directions[i] = true;
183 PcapPacket currPkt = pktSequence.get(i);
184 PcapPacket prevPkt = pktSequence.get(i-1);
185 if (PcapPacketUtils.getEthSrcAddr(currPkt).equals(PcapPacketUtils.getEthSrcAddr(prevPkt))) {
186 // Same direction as previous packet.
187 directions[i] = directions[i-1];
189 // Opposite direction of previous packet.
190 directions[i] = !directions[i-1];