1) Skip zero-payload packets when reassemlbing conversations from individual packets...
[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.packet.IpV4Packet;
7 import org.pcap4j.packet.Packet;
8 import org.pcap4j.packet.TcpPacket;
9
10 import java.io.EOFException;
11 import java.util.*;
12 import java.util.concurrent.TimeoutException;
13
14 /**
15  * Provides functionality for searching for the presence of a {@link FlowPattern} in a PCAP trace.
16  *
17  * @author Janus Varmarken
18  */
19 public class FlowPatternFinder {
20
21     private final Map<String, Set<String>> dnsMap;
22     private final Map<Conversation, List<Packet>> connections = new HashMap<>();
23
24     public FlowPatternFinder(Map<String, Set<String>> dnsMap) {
25         this.dnsMap = Objects.requireNonNull(dnsMap);
26     }
27
28     private static final Set<String> EMPTY_SET = Collections.unmodifiableSet(new HashSet<>());
29
30     // TODO clean up exceptions etc.
31     public void findFlowPattern(PcapHandle pcap, FlowPattern pattern)
32             throws PcapNativeException, NotOpenException, TimeoutException {
33         try {
34             Packet packet;
35             while ((packet = pcap.getNextPacketEx()) != null) {
36
37                 // For now, we only work support pattern search in TCP over IPv4.
38                 IpV4Packet ipPacket = packet.get(IpV4Packet.class);
39                 TcpPacket tcpPacket = packet.get(TcpPacket.class);
40                 if (ipPacket == null || tcpPacket == null) {
41                     continue;
42                 }
43                 String srcAddress = ipPacket.getHeader().getSrcAddr().getHostAddress();
44                 String dstAddress = ipPacket.getHeader().getDstAddr().getHostAddress();
45                 int srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
46                 int dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
47                 // Is this packet related to the pattern and coming from the cloud server?
48                 boolean fromServer = dnsMap.getOrDefault(srcAddress, EMPTY_SET).contains(pattern.getHostname());
49                 // Is this packet related to the pattern and going to the cloud server?
50                 boolean fromClient = dnsMap.getOrDefault(dstAddress, EMPTY_SET).contains(pattern.getHostname());
51                 if (!fromServer && !fromClient) {
52                     // Packet not related to pattern, skip it.
53                     continue;
54                 }
55                 if (tcpPacket.getPayload() == null) {
56                     // We skip non-payload control packets as these are less predictable and should therefore not be
57                     // part of a signature (e.g. receiver can choose not to ACK immediately)
58                     continue;
59                 }
60
61                 // Identify conversations (connections/sessions) by the four-tuple (clientIp, clientPort, serverIp, serverPort).
62                 // TODO: this is strictly not sufficient to differentiate one TCP session from another, but should suffice for now.
63                 Conversation conversation = fromClient ? new Conversation(srcAddress, srcPort, dstAddress, dstPort) :
64                         new Conversation(dstAddress, dstPort, srcAddress, srcPort);
65                 List<Packet> listWrappedPacket = new ArrayList<>();
66                 listWrappedPacket.add(packet);
67                 // Create new conversation entry, or append packet to existing.
68                 connections.merge(conversation, listWrappedPacket, (v1, v2) -> {
69                     v1.addAll(v2);
70                     return v1;
71                 });
72             }
73         } catch (EOFException eofe) {
74             System.out.println("findFlowPattern: finished processing entire file");
75             find(pattern);
76         }
77     }
78
79     private void find(FlowPattern pattern) {
80         for (Conversation con : connections.keySet()) {
81             List<Packet> packets = connections.get(con);
82             if (packets.size() != pattern.getPacketOrder().size()) {
83                 // Not a complete match if different number of packets.
84                 continue;
85             }
86             boolean completeMatch = true;
87             for (int i = 0; i < packets.size(); i++) {
88                 TcpPacket tcpPacket = packets.get(i).get(TcpPacket.class);
89                 if (tcpPacket.getPayload().length() != pattern.getPacketOrder().get(i)) {
90                     completeMatch = false;
91                     break;
92                 }
93             }
94             if (completeMatch) {
95                 System.out.println(String.format("found a complete match for %s", pattern.getPatternId()));
96             }
97         }
98     }
99
100     /**
101      * Immutable class used for identifying a conversation/connection/session/flow (packet's belonging to the same
102      * session between a client and a server).
103      */
104     private static class Conversation {
105
106         private final String clientIp;
107         private final int clientPort;
108         private final String serverIp;
109         private final int serverPort;
110
111         public Conversation(String clientIp, int clientPort, String serverIp, int serverPort) {
112             this.clientIp = clientIp;
113             this.clientPort = clientPort;
114             this.serverIp = serverIp;
115             this.serverPort = serverPort;
116         }
117
118
119         // =========================================================================================================
120         // We simply reuse equals and hashCode methods of String.class to be able to use this immutable class as a key
121         // in a Map.
122         @Override
123         public boolean equals(Object obj) {
124             return obj instanceof Conversation && this.toString().equals(obj.toString());
125         }
126         @Override
127         public int hashCode() {
128             return toString().hashCode();
129         }
130         // =========================================================================================================
131
132         @Override
133         public String toString() {
134             return String.format("%s:%d %s:%d", clientIp, clientPort, serverIp, serverPort);
135         }
136     }
137
138 }