Adding PacketLevelSignatureExtractor.
[pingpong.git] / Code / Projects / PacketLevelSignatureExtractor / src / main / java / edu / uci / iotproject / analysis / PcapPacketPair.java
1 package edu.uci.iotproject.analysis;
2
3 import edu.uci.iotproject.DnsMap;
4 import edu.uci.iotproject.util.PcapPacketUtils;
5 import org.apache.commons.math3.stat.clustering.Clusterable;
6 import org.pcap4j.core.PcapPacket;
7
8 import java.net.InetAddress;
9 import java.net.UnknownHostException;
10 import java.util.Collection;
11 import java.util.Optional;
12 import java.util.Set;
13 import java.util.stream.Collectors;
14
15 import static edu.uci.iotproject.util.PcapPacketUtils.getSourceIp;
16
17 /**
18  * <p>
19  *     A simple wrapper for holding a pair of packets (e.g., a request and associated reply packet).
20  * </p>
21  *
22  * <b>Note:</b> we use the deprecated version
23  *
24  * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
25  * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
26  */
27 public class PcapPacketPair implements Clusterable<PcapPacketPair> {
28
29     /**
30      * If {@code true}, {@link #distanceFrom(PcapPacketPair)} will only consider if the sources of the two packets in
31      * the {@link PcapPacketPair}s being compared match in terms of whether the IP is a local or a remote IP. It will
32      * <em>not</em> check if the IPs/hostnames are actually the same. Set to {@code false} to make the comparison more
33      * strict, i.e., to enforce the requirement that the respective IPs (or hostnames) in the packets of the two
34      * {@link PcapPacketPair}s must be identical.
35      */
36     private static final boolean SIMPLIFIED_SOURCE_COMPARISON = true;
37
38     private final PcapPacket mFirst;
39
40     private final Optional<PcapPacket> mSecond;
41
42     /**
43      * IP to hostname mappings.
44      * Allows for grouping packets with different source IPs that map to the same hostname into one cluster.
45      */
46     private DnsMap mDnsMap; // TODO implement and invoke setter
47
48     public PcapPacketPair(PcapPacket first, PcapPacket second) {
49         mFirst = first;
50         mSecond = Optional.ofNullable(second);
51     }
52
53     public PcapPacket getFirst() { return mFirst; }
54
55     public boolean isFirstClient() {
56         String firstIp = PcapPacketUtils.getSourceIp(mFirst);
57         InetAddress ia = null;
58         try {
59             ia = InetAddress.getByName(firstIp);
60         } catch (UnknownHostException ex) {
61             ex.printStackTrace();
62         }
63         return ia.isSiteLocalAddress();
64     }
65
66     public Optional<PcapPacket> getSecond() { return mSecond; }
67
68     public boolean isSecondClient() {
69         // Return the value of the second source if it is not null
70         if (mSecond.isPresent()) {
71             String secondIp = PcapPacketUtils.getSourceIp(mSecond.get());
72             InetAddress ia = null;
73             try {
74                 ia = InetAddress.getByName(secondIp);
75             } catch (UnknownHostException ex) {
76                 ex.printStackTrace();
77             }
78             return ia.isSiteLocalAddress();
79         } else {
80             // When it is null, we always return the opposite of the first source's status
81             return !isFirstClient();
82         }
83     }
84
85     /**
86      * Get the {@link DnsMap} that is queried for hostnames mappings when performing IP/hostname-sensitive clustering.
87      * @return the {@link DnsMap} that is queried for hostnames mappings when performing IP/hostname-sensitive clustering.
88      */
89     public DnsMap getDnsMap() {
90         return mDnsMap;
91     }
92
93     /**
94      * Set the {@link DnsMap} to be queried for hostnames mappings when performing IP/hostname-sensitive clustering.
95      * @param dnsMap a {@code DnsMap} to be queried for hostnames mappings when performing IP/hostname-sensitive clustering.
96      */
97     public void setDnsMap(final DnsMap dnsMap) {
98         mDnsMap = dnsMap;
99     }
100
101     @Override
102     public String toString() {
103         return String.format("%d, %s",
104                 getFirst().getOriginalLength(),
105                 getSecond().map(pkt -> Integer.toString(pkt.getOriginalLength())).orElse("null"));
106     }
107
108     // =================================================================================================================
109     // Begin implementation of org.apache.commons.math3.stat.clustering.Clusterable interface
110     @Override
111     public double distanceFrom(PcapPacketPair that) {
112         if (SIMPLIFIED_SOURCE_COMPARISON) {
113             // Direction of packets in terms of client-to-server or server-to-client must match, but we don't care about
114             // IPs and hostnames
115             if (this.isFirstClient() != that.isFirstClient() || this.isSecondClient() != that.isSecondClient()) {
116                 // Distance is maximal if mismatch in direction of packets
117                 return Double.MAX_VALUE;
118             }
119         } else {
120             // Strict mode enabled: IPs/hostnames must match!
121             // Extract src ips of both packets of each pair.
122             String thisSrc1 = getSourceIp(this.getFirst());
123             String thisSrc2 = this.getSecond().map(pp -> getSourceIp(pp)).orElse("");
124             String thatSrc1 = getSourceIp(that.getFirst());
125             String thatSrc2 = that.getSecond().map(pp -> getSourceIp(pp)).orElse("");
126
127             // Replace IPs with hostnames if possible.
128             thisSrc1 = mapToHostname(thisSrc1);
129             thisSrc2 = mapToHostname(thisSrc2);
130             thatSrc1 = mapToHostname(thatSrc1);
131             thatSrc2 = mapToHostname(thatSrc2);
132
133             if(!thisSrc1.equals(thatSrc1) || !thisSrc2.equals(thatSrc2)) {
134                 // Distance is maximal if sources differ.
135                 return Double.MAX_VALUE;
136             }
137         }
138
139         // If the sources match, the distance is the Euclidean distance between each pair of packet lengths.
140         int thisLen1 = this.getFirst().getOriginalLength();
141         // TODO should discard pairs w/o second packet from clustering; replace below with getSecond().get() when done.
142         int thisLen2 = this.getSecond().map(pp -> pp.getOriginalLength()).orElse(0);
143         int thatLen1 = that.getFirst().getOriginalLength();
144         // TODO should discard pairs w/o second packet from clustering; replace below with getSecond().get() when done.
145         int thatLen2 = that.getSecond().map(pp -> pp.getOriginalLength()).orElse(0);
146         return Math.sqrt(
147                 Math.pow(thisLen1 - thatLen1, 2) +
148                         Math.pow(thisLen2 - thatLen2, 2)
149         );
150     }
151
152     @Override
153     public PcapPacketPair centroidOf(Collection<PcapPacketPair> p) {
154         // No notion of centroid in DBSCAN
155         throw new UnsupportedOperationException("Not implemented; no notion of a centroid in DBSCAN.");
156     }
157     // End implementation of org.apache.commons.math3.stat.clustering.Clusterable interface
158     // =================================================================================================================
159
160     private String mapToHostname(String ip) {
161         Set<String> hostnames = mDnsMap.getHostnamesForIp(ip);
162         if (hostnames != null && hostnames.size() > 0) {
163             // append hostnames back-to-back separated by a delimiter if more than one item in set
164             // note: use sorted() to ensure that output remains consistent (as Set has no internal ordering of elements)
165             String result = hostnames.stream().sorted().collect(Collectors.joining(" "));
166             if (hostnames.size() > 1) {
167                 // One IP can map to multiple hostnames, although that is rare. For now just raise a warning.
168                 String warningStr = String.format(
169                         "%s.mapToHostname(): encountered an IP (%s) that maps to multiple hostnames (%s)",
170                         getClass().getSimpleName(), ip, result);
171                 System.err.println(warningStr);
172             }
173             return result;
174         }
175         // If unable to map to a hostname, return ip for ease of use; caller can overwrite input value, defaulting to
176         // the original value if no mapping is found:
177         // String src = "<some-ip>";
178         // src = mapToHostname(src); // src is now either a hostname or the original ip.
179         return ip;
180     }
181
182 }