preliminary work on signature detection
[pingpong.git] / Code / Projects / SmartPlugDetector / src / main / java / edu / uci / iotproject / util / PcapPacketUtils.java
1 package edu.uci.iotproject.util;
2
3 import edu.uci.iotproject.Conversation;
4 import edu.uci.iotproject.analysis.PcapPacketPair;
5 import edu.uci.iotproject.analysis.TcpConversationUtils;
6 import org.apache.commons.math3.stat.clustering.Cluster;
7 import org.pcap4j.core.PcapPacket;
8 import org.pcap4j.packet.IpV4Packet;
9 import org.pcap4j.packet.TcpPacket;
10
11 import java.util.*;
12
13 /**
14  * Utility methods for inspecting {@link PcapPacket} properties.
15  *
16  * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
17  * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
18  */
19 public final class PcapPacketUtils {
20
21     /**
22      * This is the threshold value for a signature's number of members
23      * If after a merging the number of members of a signature falls below this threshold, then we can boldly
24      * get rid of that signature.
25      */
26     private static final int SIGNATURE_MERGE_THRESHOLD = 5;
27
28     /**
29      * Determines if a given {@link PcapPacket} wraps a {@link TcpPacket}.
30      * @param packet The {@link PcapPacket} to inspect.
31      * @return {@code true} if {@code packet} wraps a {@link TcpPacket}, {@code false} otherwise.
32      */
33     public static boolean isTcp(PcapPacket packet) {
34         return packet.get(TcpPacket.class) != null;
35     }
36
37     /**
38      * Gets the source IP (in decimal format) of an IPv4 packet.
39      * @param packet The packet for which the IPv4 source address is to be extracted.
40      * @return The decimal representation of the source IP of {@code packet} <em>iff</em> {@code packet} wraps an
41      *         {@link IpV4Packet}, otherwise {@code null}.
42      */
43     public static String getSourceIp(PcapPacket packet) {
44         IpV4Packet ipPacket = packet.get(IpV4Packet.class);
45         return ipPacket == null ? null : ipPacket.getHeader().getSrcAddr().getHostAddress();
46     }
47
48     /**
49      * Gets the source port of a TCP packet.
50      * @param packet The packet for which the source port is to be extracted.
51      * @return The source port of the {@link TcpPacket} encapsulated by {@code packet}.
52      * @throws IllegalArgumentException if {@code packet} does not encapsulate a {@link TcpPacket}.
53      */
54     public static int getSourcePort(PcapPacket packet) {
55         TcpPacket tcpPacket = packet.get(TcpPacket.class);
56         if (tcpPacket == null) {
57             throw new IllegalArgumentException("not a TCP packet");
58         }
59         return tcpPacket.getHeader().getSrcPort().valueAsInt();
60     }
61
62     /**
63      * Gets the destination port of a TCP packet.
64      * @param packet The packet for which the destination port is to be extracted.
65      * @return The destination port of the {@link TcpPacket} encapsulated by {@code packet}.
66      * @throws IllegalArgumentException if {@code packet} does not encapsulate a {@link TcpPacket}.
67      */
68     public static int getDestinationPort(PcapPacket packet) {
69         TcpPacket tcpPacket = packet.get(TcpPacket.class);
70         if (tcpPacket == null) {
71             throw new IllegalArgumentException("not a TCP packet");
72         }
73         return tcpPacket.getHeader().getDstPort().valueAsInt();
74     }
75
76     /**
77      * Helper method to determine if the given combination of IP and port matches the source of the given packet.
78      * @param packet The packet to check.
79      * @param ip The IP to look for in the ip.src field of {@code packet}.
80      * @param port The port to look for in the tcp.port field of {@code packet}.
81      * @return {@code true} if the given ip+port match the corresponding fields in {@code packet}.
82      */
83     public static boolean isSource(PcapPacket packet, String ip, int port) {
84         IpV4Packet ipPacket = Objects.requireNonNull(packet.get(IpV4Packet.class));
85         // For now we only support TCP flows.
86         TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
87         String ipSrc = ipPacket.getHeader().getSrcAddr().getHostAddress();
88         int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
89         return ipSrc.equals(ip) && srcPort == port;
90     }
91
92     /**
93      * Helper method to determine if the given combination of IP and port matches the destination of the given packet.
94      * @param packet The packet to check.
95      * @param ip The IP to look for in the ip.dst field of {@code packet}.
96      * @param port The port to look for in the tcp.dstport field of {@code packet}.
97      * @return {@code true} if the given ip+port match the corresponding fields in {@code packet}.
98      */
99     public static boolean isDestination(PcapPacket packet, String ip, int port) {
100         IpV4Packet ipPacket = Objects.requireNonNull(packet.get(IpV4Packet.class));
101         // For now we only support TCP flows.
102         TcpPacket tcpPacket = Objects.requireNonNull(packet.get(TcpPacket.class));
103         String ipDst = ipPacket.getHeader().getDstAddr().getHostAddress();
104         int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
105         return ipDst.equals(ip) && dstPort == port;
106     }
107
108     /**
109      * Checks if {@code packet} wraps a TCP packet that has the SYN flag set.
110      * @param packet A {@link PcapPacket} that is suspected to contain a {@link TcpPacket} for which the SYN flag is set.
111      * @return {@code true} <em>iff</em> {@code packet} contains a {@code TcpPacket} for which the SYN flag is set,
112      *         {@code false} otherwise.
113      */
114     public static boolean isSyn(PcapPacket packet) {
115         TcpPacket tcp = packet.get(TcpPacket.class);
116         return tcp != null && tcp.getHeader().getSyn();
117     }
118
119     /**
120      * Checks if {@code packet} wraps a TCP packet that has the ACK flag set.
121      * @param packet A {@link PcapPacket} that is suspected to contain a {@link TcpPacket} for which the ACK flag is set.
122      * @return {@code true} <em>iff</em> {@code packet} contains a {@code TcpPacket} for which the ACK flag is set,
123      *         {@code false} otherwise.
124      */
125     public static boolean isAck(PcapPacket packet) {
126         TcpPacket tcp = packet.get(TcpPacket.class);
127         return tcp != null && tcp.getHeader().getAck();
128     }
129
130     /**
131      * Transform a {@code Cluster} of {@code PcapPacketPair} objects into a {@code List} of {@code List} of
132      * {@code PcapPacket} objects.
133      * @param cluster A {@link Cluster} of {@link PcapPacketPair} objects that needs to be transformed.
134      * @return A {@link List} of {@link List} of {@link PcapPacket} objects as the result of the transformation.
135      */
136     public static List<List<PcapPacket>> clusterToListOfPcapPackets(Cluster<PcapPacketPair> cluster) {
137         List<List<PcapPacket>> ppListOfList = new ArrayList<>();
138         for (PcapPacketPair ppp: cluster.getPoints()) {
139             // Create a list of PcapPacket objects (list of two members)
140             List<PcapPacket> ppList = new ArrayList<>();
141             ppList.add(ppp.getFirst());
142             if(ppp.getSecond().isPresent())
143                 ppList.add(ppp.getSecond().get());
144             else
145                 ppList.add(null);
146             // Create a list of list of PcapPacket objects
147             ppListOfList.add(ppList);
148         }
149         // Sort the list of lists based on the first packet's timestamp!
150         Collections.sort(ppListOfList, (p1, p2) -> p1.get(0).getTimestamp().compareTo(p2.get(0).getTimestamp()));
151         return ppListOfList;
152     }
153
154     /**
155      * Merge signatures in {@code List} of {@code List} of {@code List} of {@code PcapPacket} objects.
156      * We cross-check these with {@code List} of {@code Conversation} objects to see
157      * if two {@code List} of {@code PcapPacket} objects actually belong to the same {@code Conversation}.
158      *
159      * @param signatures A {@link List} of {@link List} of {@link List} of
160      *          {@link PcapPacket} objects that needs to be checked and merged.
161      * @param conversations A {@link List} of {@link Conversation} objects as reference for merging.
162      * @return A {@link List} of {@link List} of {@link List} of
163      *          {@link PcapPacket} objects as the result of the merging.
164      */
165     public static List<List<List<PcapPacket>>>
166             mergeSignatures(List<List<List<PcapPacket>>> signatures, List<Conversation> conversations) {
167         // Make a copy first
168         List<List<List<PcapPacket>>> copySignatures = new ArrayList<>(signatures);
169         // Traverse and look into the pairs of signatures
170         for (int first = 0; first < signatures.size(); first++) {
171             List<List<PcapPacket>> firstList = signatures.get(first);
172             for (int second = first+1; second < signatures.size(); second++) {
173                 int maxSignatureEl = 0; // Number of maximum signature elements
174                 List<List<PcapPacket>> secondList = signatures.get(second);
175                 int initialSecondListMembers = secondList.size();
176                 // Iterate over the signatures in the first list
177                 for (List<PcapPacket> signature : firstList) {
178                     signature.removeIf(el -> el == null); // Clean up null elements
179                     // Return the Conversation that the signature is part of
180                     Conversation conv = TcpConversationUtils.returnConversation(signature, conversations);
181                     // Find the element of the second list that is a match for that Conversation
182                     for (List<PcapPacket> ppList : secondList) {
183                         ppList.removeIf(el -> el == null); // Clean up null elements
184                         // Check if they are part of a Conversation and are adjacent to the first signature
185                         // If yes then merge into the first list
186                         TcpConversationUtils.SignaturePosition position =
187                                 TcpConversationUtils.isPartOfConversationAndAdjacent(signature, ppList, conv);
188                         if (position == TcpConversationUtils.SignaturePosition.LEFT_ADJACENT) {
189                             // Merge to the left side of the first signature
190                             ppList.addAll(signature);
191                             signature = ppList;
192                             maxSignatureEl = signature.size() > maxSignatureEl ? signature.size() : maxSignatureEl;
193                             secondList.remove(ppList); // Remove as we merge
194                             //System.out.println("LEFT_ADJACENT!");
195                             break;
196                         } else if (position == TcpConversationUtils.SignaturePosition.RIGHT_ADJACENT) {
197                             // Merge to the right side of the first signature
198                             signature.addAll(ppList);
199                             maxSignatureEl = signature.size() > maxSignatureEl ? signature.size() : maxSignatureEl;
200                             secondList.remove(ppList); // Remove as we merge
201                             //System.out.println("RIGHT_ADJACENT!");
202                             break;
203                         } // TcpConversationUtils.SignaturePosition.NOT_ADJACENT
204                         //System.out.println("NOT_ADJACENT!");
205                     }
206                 }
207                 // Call it a successful merging if there are only less than 5 elements from the second list that
208                 // cannot be merged
209                 if (secondList.size() < SIGNATURE_MERGE_THRESHOLD) {
210                     // Prune the unsuccessfully merged signatures (i.e., these will have size() < maxSignatureEl)
211                     final int maxNumOfEl = maxSignatureEl;
212                     firstList.removeIf(el -> el.size() < maxNumOfEl);
213                     // Remove the merged set of signatures when successful
214                     signatures.remove(secondList);
215                 } else if (secondList.size() < initialSecondListMembers) {
216                     // If only some of the signatures from the second list are merged, this means UNSUCCESSFUL merging
217                     // Return the original copy of the signatures object
218                     return copySignatures;
219                 }
220             }
221         }
222         return signatures;
223     }
224 }