Making retransmission check O(1) using HashSet/Set.
[pingpong.git] / Code / Projects / SmartPlugDetector / src / main / java / edu / uci / iotproject / FlowPatternFinder.java
1 package edu.uci.iotproject;
2
3 import org.pcap4j.core.NotOpenException;
4 import org.pcap4j.core.PcapHandle;
5 import org.pcap4j.core.PcapNativeException;
6 import org.pcap4j.core.PcapPacket;
7 import org.pcap4j.packet.IpV4Packet;
8 import org.pcap4j.packet.Packet;
9 import org.pcap4j.packet.TcpPacket;
10 import org.pcap4j.packet.DnsPacket;
11
12 import java.io.EOFException;
13 import java.net.UnknownHostException;
14 import java.time.Instant;
15 import java.util.*;
16 import java.util.concurrent.TimeoutException;
17
18 /**
19  * Provides functionality for searching for the presence of a {@link FlowPattern} in a PCAP trace.
20  *
21  * @author Janus Varmarken
22  */
23 public class FlowPatternFinder {
24
25     /* Class properties */
26     private final Map<Conversation, List<PcapPacket>> connections = new HashMap<>();
27     private DnsMap dnsMap;
28     
29     
30     /* Constructor */
31     public FlowPatternFinder() {
32         this.dnsMap = new DnsMap();
33     }
34
35
36     /**
37      * Find pattern based on the FlowPattern object
38      *
39      * @param   pcap        PCAP file handler
40      * @param   pattern     FlowPattern class object as a comparator
41      */
42     // TODO clean up exceptions etc.
43     public void findFlowPattern(PcapHandle pcap, FlowPattern pattern)
44             throws PcapNativeException, NotOpenException, TimeoutException {
45         int counter = 0;
46         try {
47             PcapPacket packet;
48             Set<Integer> seqNumberSet = new HashSet<Integer>();
49             while ((packet = pcap.getNextPacketEx()) != null) {
50
51                 // Check if this is a valid DNS packet
52                 dnsMap.validateAndAddNewEntry(packet);
53                 // For now, we only work support pattern search in TCP over IPv4.
54                 IpV4Packet ipPacket = packet.get(IpV4Packet.class);
55                 TcpPacket tcpPacket = packet.get(TcpPacket.class);
56                 if (ipPacket == null || tcpPacket == null) {
57                     continue;
58                 }
59                 String srcAddress = ipPacket.getHeader().getSrcAddr().getHostAddress();
60                 String dstAddress = ipPacket.getHeader().getDstAddr().getHostAddress();
61                 int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
62                 int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
63                 // Is this packet related to the pattern and coming from the cloud server?
64                 boolean fromServer = dnsMap.isRelatedToCloudServer(srcAddress, pattern.getHostname());
65                 // Is this packet related to the pattern and going to the cloud server?
66                 boolean fromClient = dnsMap.isRelatedToCloudServer(dstAddress, pattern.getHostname());
67                 if (!fromServer && !fromClient) {
68                     // Packet not related to pattern, skip it.
69                     continue;
70                 }
71                 if (tcpPacket.getPayload() == null) {
72                     // We skip non-payload control packets as these are less predictable and should therefore not be
73                     // part of a signature (e.g. receiver can choose not to ACK immediately)
74                     continue;
75                 }
76
77                 // Identify conversations (connections/sessions) by the four-tuple (clientIp, clientPort, serverIp, serverPort).
78                 // TODO: this is strictly not sufficient to differentiate one TCP session from another, but should suffice for now.
79                 Conversation conversation = fromClient ? new Conversation(srcAddress, srcPort, dstAddress, dstPort) :
80                         new Conversation(dstAddress, dstPort, srcAddress, srcPort);
81                 List<PcapPacket> listWrappedPacket = new ArrayList<>();               
82                 listWrappedPacket.add(packet);
83                 // Create new conversation entry, or append packet to existing.
84                 connections.merge(conversation, listWrappedPacket, (v1, v2) -> {
85                     int seqNumber = v2.get(0).get(TcpPacket.class).getHeader().getSequenceNumber();
86                     boolean retransmission = seqNumberSet.contains(seqNumber);
87                     if (!retransmission) {
88                         // Do not add if retransmission -> avoid duplicate packets in flow
89                         v1.addAll(v2);
90                         seqNumberSet.add(seqNumber);
91                     }
92                     return v1;
93                 });
94             }
95         } catch (EOFException eofe) {
96             System.out.println("findFlowPattern: finished processing entire file");
97             find(pattern);
98         } catch (UnknownHostException ex) {
99             System.out.println(); 
100             ex.printStackTrace();
101         }
102     }
103
104
105     private void find(FlowPattern pattern) {
106         for (Conversation con : connections.keySet()) {
107             List<PcapPacket> packets = connections.get(con);
108             if (packets.size() != pattern.getPacketOrder().size()) {
109                 // Not a complete match if different number of packets.
110                 continue;
111             }
112             boolean completeMatch = true;
113             for (int i = 0; i < packets.size(); i++) {
114                 TcpPacket tcpPacket = packets.get(i).get(TcpPacket.class);
115                 if (tcpPacket.getPayload().length() != pattern.getPacketOrder().get(i)) {
116                     completeMatch = false;
117                     break;
118                 }
119             }
120             if (completeMatch) {
121                 PcapPacket firstPacketInFlow = packets.get(0);
122                 System.out.println(
123                         String.format("[ detected a complete match of pattern '%s' at %s]",
124                                 pattern.getPatternId(), firstPacketInFlow.getTimestamp().toString()));
125             }
126         }
127     }
128
129
130     /**
131      * Immutable class used for identifying a conversation/connection/session/flow (packet's belonging to the same
132      * session between a client and a server).
133      */
134     private static class Conversation {
135
136         private final String clientIp;
137         private final int clientPort;
138         private final String serverIp;
139         private final int serverPort;
140
141         public Conversation(String clientIp, int clientPort, String serverIp, int serverPort) {
142             this.clientIp = clientIp;
143             this.clientPort = clientPort;
144             this.serverIp = serverIp;
145             this.serverPort = serverPort;
146         }
147
148
149         // =========================================================================================================
150         // We simply reuse equals and hashCode methods of String.class to be able to use this immutable class as a key
151         // in a Map.
152         @Override
153         public boolean equals(Object obj) {
154             return obj instanceof Conversation && this.toString().equals(obj.toString());
155         }
156         @Override
157         public int hashCode() {
158             return toString().hashCode();
159         }
160         // =========================================================================================================
161
162         @Override
163         public String toString() {
164             return String.format("%s:%d %s:%d", clientIp, clientPort, serverIp, serverPort);
165         }
166     }
167
168 }