Adding PacketLevelSignatureExtractor.
[pingpong.git] / Code / Projects / PacketLevelSignatureExtractor / src / main / java / edu / uci / iotproject / util / PrintUtils.java
1 package edu.uci.iotproject.util;
2
3 import edu.uci.iotproject.DnsMap;
4 import edu.uci.iotproject.analysis.PcapPacketPair;
5 import org.apache.commons.math3.stat.clustering.Cluster;
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.util.List;
12 import java.util.Optional;
13 import java.util.Set;
14 import java.util.stream.Collectors;
15
16 import org.pcap4j.core.PcapPacket;
17
18 /**
19  * Utility methods for generating (output) strings.
20  *
21  * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
22  * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
23  */
24 public class PrintUtils {
25
26     /**
27      * This is the path for writing the list of list of packet pairs {@code List<List<PcapPacket>>} into a file.
28      * The packet pairs are the pairs in one cluster, so the list represents a cluster that has been derived through
29      * the DBSCAN algorithm.
30      *
31      * E.g., this file could contain a list like the following:
32      *
33      * [[1109, 613],[1111, 613],[1115, 613],...]
34      *
35      * This list has lists of PcapPacket pairs as its members. We do not maintain the pairs in the form of
36      * {@code Cluster<PcapPacketPair>} objects because there might be a situation where we could combine multiple
37      * PcapPacketPair objects into a longer signature, i.e., a string of PcapPacket objects and not just a pair.
38      */
39     private static final String SERIALIZABLE_FILE_PATH = "./signature.sig";
40
41     private PrintUtils() { /* private constructor to prevent instantiation */ }
42
43     /**
44      * Write the list of list of packet pairs {@code List<List<PcapPacket>>} into a file.
45      *
46      * After the DBSCAN algorithm derives the clusters from pairs, we save the signature in the form of list of
47      * packet pairs. We harvest the pairs and transform them back into a list of PcapPacket objects.
48      * We do not maintain the pairs in the form of {@code Cluster<PcapPacketPair>} objects because there might be
49      * a situation where we could combine multiple PcapPacketPair objects into a longer signature, i.e., a string of
50      * PcapPacket objects and not just a pair.
51      *
52      * @param fileName The path of the file in {@link String}. We could leave this one {@code null} if we wanted the
53      *                 default file name {@code SERIALIZABLE_FILE_PATH}.
54      * @param clusterPackets The {@link Cluster} objects in the form of list of {@code PcapPacket} objects.
55      */
56     public static void serializeClustersIntoFile(String fileName, List<List<PcapPacket>> clusterPackets) {
57         if (fileName == null)
58             fileName = SERIALIZABLE_FILE_PATH;
59         try (ObjectOutputStream oos =
60                 new ObjectOutputStream(new FileOutputStream(fileName))) {
61             oos.writeObject(clusterPackets);
62         } catch (Exception ex) {
63             ex.printStackTrace();
64         }
65     }
66
67     /**
68      * Write the signature {@code List<List<List<PcapPacket>>>} into a file.
69      *
70      * After the DBSCAN algorithm derives the clusters from pairs, we save the signature in the form of list of
71      * packet pairs. We harvest the pairs and transform them back into a list of PcapPacket objects.
72      * We do not maintain the pairs in the form of {@code Cluster<PcapPacketPair>} objects because there might be
73      * a situation where we could combine multiple PcapPacketPair objects into a longer signature, i.e., a string of
74      * PcapPacket objects and not just a pair.
75      *
76      * @param fileName The path of the file in {@link String}. We could leave this one {@code null} if we wanted the
77      *                 default file name {@code SERIALIZABLE_FILE_PATH}.
78      * @param signature The {@link Cluster} objects in the form of list of {@code PcapPacket} objects.
79      */
80     public static void serializeSignatureIntoFile(String fileName, List<List<List<PcapPacket>>> signature) {
81         if (fileName == null)
82             fileName = SERIALIZABLE_FILE_PATH;
83         try (ObjectOutputStream oos =
84                      new ObjectOutputStream(new FileOutputStream(fileName))) {
85             oos.writeObject(signature);
86         } catch (Exception ex) {
87             ex.printStackTrace();
88         }
89     }
90
91     /**
92      * Read the list of list of packet pairs {@code List<List<PcapPacket>>} from a file.
93      *
94      * After the DBSCAN algorithm derives the clusters from pairs, we save the signature in the form of list of
95      * packet pairs. We harvest the pairs and transform them back into a list of PcapPacket objects.
96      * We do not maintain the pairs in the form of {@code Cluster<PcapPacketPair>} objects because there might be
97      * a situation where we could combine multiple PcapPacketPair objects into a longer signature, i.e., a string of
98      * PcapPacket objects and not just a pair.
99      *
100      * @param fileName The path of the file in {@link String}. We could leave this one {@code null} if we wanted the
101      *                 default file name {@code SERIALIZABLE_FILE_PATH}.
102      * @return The list of list of {@link Cluster} objects ({@code List<List<PcapPacket>>}) that is read from file.
103      */
104     public static List<List<PcapPacket>> deserializeClustersFromFile(String fileName) {
105         if (fileName == null)
106             fileName = SERIALIZABLE_FILE_PATH;
107         List<List<PcapPacket>> ppListOfList = null;
108         try (ObjectInputStream ois =
109                      new ObjectInputStream(new FileInputStream(fileName))) {
110             ppListOfList = (List<List<PcapPacket>>) ois.readObject();
111         } catch (Exception ex) {
112             ex.printStackTrace();
113         }
114
115         return ppListOfList;
116     }
117
118     /**
119      * Read the list of list of packet pairs {@code List<List<List<PcapPacket>>>} from a file.
120      *
121      * After the DBSCAN algorithm derives the clusters from pairs, we save the signature in the form of list of
122      * packet pairs. We harvest the pairs and transform them back into a list of PcapPacket objects.
123      * We do not maintain the pairs in the form of {@code Cluster<PcapPacketPair>} objects because there might be
124      * a situation where we could combine multiple PcapPacketPair objects into a longer signature, i.e., a string of
125      * PcapPacket objects and not just a pair.
126      *
127      * @param fileName The path of the file in {@link String}. We could leave this one {@code null} if we wanted the
128      *                 default file name {@code SERIALIZABLE_FILE_PATH}.
129      * @return The list of list of list of {@link Cluster} objects ({@code List<List<List<PcapPacket>>>})
130      *         that is read from file.
131      */
132     public static List<List<List<PcapPacket>>> deserializeSignatureFromFile(String fileName) {
133         if (fileName == null)
134             fileName = SERIALIZABLE_FILE_PATH;
135         List<List<List<PcapPacket>>> ppListOfListOfList = null;
136         try (ObjectInputStream ois =
137                      new ObjectInputStream(new FileInputStream(fileName))) {
138             ppListOfListOfList = (List<List<List<PcapPacket>>>) ois.readObject();
139         } catch (Exception ex) {
140             ex.printStackTrace();
141         }
142
143         return ppListOfListOfList;
144     }
145
146     /**
147      * Converts a {@code PcapPacketPair} into a CSV string containing the packet lengths of the two packets in the pair.
148      *
149      * For example, the resulting string will be "123, 456" if the first packet of the pair has a length of 123 and the
150      * second packet of the pair has a length of 456.
151      *
152      * <b>Note:</b> if the {@link PcapPacketPair} has no second element, 0 is printed as the length of the second packet
153      * in the pair.
154      *
155      * @return a CSV string containing the packet lengths of the two packets of the given {@code PcapPacketPair}.
156      */
157     public static String toCsv(PcapPacketPair packetPair) {
158         return String.format("%d, %d", packetPair.getFirst().getOriginalLength(),
159                 packetPair.getSecond().map(pp -> pp.getOriginalLength()).orElse(0));
160     }
161
162     /**
163      * Converts a {@code PcapPacketPair} into a CSV string containing the packet lengths of the two packets in the pair
164      * followed by the source of each packet. The source will be a (set of) hostname(s) if the source IP can be resolved
165      * to a (set of) hostname(s) using the provided {@link DnsMap}.
166      *
167      * For example, the resulting string will be "123, 456, 192.168.1.42, domain.com" if the first packet of the pair
168      * has a length of 123, the second packet of the pair has a length of 456, the first packet of the pair the pair has
169      * a source IP of '192.168.1.42' that cannot be resolved to a hostname, and the second packet of the pair has an IP
170      * that resolves to 'domain.com'.
171      *
172      * <b>Note:</b> if the {@link PcapPacketPair} has no second element, 0 is printed as the length of the second packet
173      * in the pair, and null is printed for its source.
174      *
175      * @return a CSV string containing the packet lengths of the two packets of the given {@code PcapPacketPair} as well
176      *         as their respective sources.
177      */
178     public static String toCsv(PcapPacketPair packetPair, DnsMap ipHostnameMappings) {
179         // First obtain source IPs
180         String firstSrc = PcapPacketUtils.getSourceIp(packetPair.getFirst());
181         // Note: use optional for second item in pair as there might not be one.
182         Optional<String> secondSrc = packetPair.getSecond().map(pkt -> PcapPacketUtils.getSourceIp(pkt));
183
184         // If possible, map source IPs to hostnames.
185         Set<String> firstHostnames = ipHostnameMappings.getHostnamesForIp(firstSrc);
186         Optional<Set<String>> secondHostnames = secondSrc.map(src -> ipHostnameMappings.getHostnamesForIp(src));
187         final String delimiter = " ";
188         if (firstHostnames != null) {
189             // If one IP maps to multiple hostnames, we concatenate the hostnames (separated by a delimiter).
190             firstSrc = firstHostnames.stream().collect(Collectors.joining(delimiter));
191         }
192         // If one IP maps to multiple hostnames, we concatenate the hostnames (separated by a delimiter).
193         Optional<String> hostnames = secondHostnames.map(hostnameSet -> hostnameSet.stream().collect(Collectors.joining(delimiter)));
194         // Fall back to IP if we couldn't second pair is present, but we couldn't map to (a) hostname(s).
195         secondSrc = hostnames.isPresent() ? hostnames : secondSrc;
196
197         // Check if the first source is C (client) or S (server).
198         String firstSrcCorS = packetPair.isFirstClient() ? "C" : "S";
199         String secondSrcCorS = packetPair.isSecondClient() ? "C" : "S";
200
201         return String.format("%d, %d, %s, %s, %s, %s", packetPair.getFirst().getOriginalLength(),
202                 packetPair.getSecond().map(pp -> pp.getOriginalLength()).orElse(0),
203                 firstSrc,
204                 secondSrc.orElse("null"),
205                 firstSrcCorS,
206                 secondSrcCorS);
207     }
208
209     /**
210      * Generate a string that summarizes/describes {@code cluster}.
211      * @param cluster The {@link Cluster} to summarize/describe.
212      * @return A string that summarizes/describes {@code cluster}.
213      */
214     public static String toSummaryString(Cluster<PcapPacketPair> cluster) {
215         StringBuilder sb = new StringBuilder();
216         for (PcapPacketPair ppp : cluster.getPoints()) {
217             sb.append(toCsv(ppp, ppp.getDnsMap()) + System.lineSeparator());
218         }
219         return sb.toString();
220     }
221 }