Modifying the value DELETED_SEQUENCES_OFF in the script from 0 to 1 as per bug/error...
[pingpong.git] / Code / Projects / PacketLevelSignatureExtractor / src / main / java / edu / uci / iotproject / trafficreassembly / layer3 / TcpReassembler.java
1 package edu.uci.iotproject.trafficreassembly.layer3;
2
3 import org.pcap4j.core.PacketListener;
4 import org.pcap4j.core.PcapPacket;
5 import org.pcap4j.packet.*;
6
7 import java.util.*;
8
9 /**
10  * Reassembles TCP conversations (streams).
11  * <b>Note: current version only supports TCP over IPv4.</b>
12  *
13  * @author Janus Varmarken {@literal <jvarmark@uci.edu>}
14  * @author Rahmadi Trimananda {@literal <rtrimana@uci.edu>}
15  */
16 public class TcpReassembler implements PacketListener {
17
18     /**
19      * Holds <em>open</em> {@link Conversation}s, i.e., {@code Conversation}s that have <em>not</em> been detected as
20      * (gracefully) terminated based on the set of packets observed thus far.
21      * A {@link Conversation} is moved to {@link #mTerminatedConversations} if it can be determined that it is has
22      * terminated. Termination can be detected by a) observing two {@link FinAckPair}s, one in each direction, (graceful
23      * termination, see {@link Conversation#isGracefullyShutdown()}) or b) by observing a SYN packet that matches the
24      * four tuple of an existing {@code Conversation}, but which holds a <em>different</em> sequence number than the
25      * same-direction SYN packet recorded for the {@code Conversation}.
26      * <p>
27      * Note that due to limitations of the {@link Set} interface (specifically, there is no {@code get(T t)} method),
28      * we have to resort to a {@link Map} (in which keys map to themselves) to "mimic" a set with {@code get(T t)}
29      * functionality.
30      *
31      * @see <a href="https://stackoverflow.com/questions/7283338/getting-an-element-from-a-set">this question on StackOverflow.com</a>
32      */
33     private final Map<Conversation, Conversation> mOpenConversations = new HashMap<>();
34
35     /**
36      * Holds <em>terminated</em> {@link Conversation}s.
37      */
38     private final List<Conversation> mTerminatedConversations = new ArrayList<>();
39                 
40                 /**
41      * IP of the router's WAN port (if analyzed traffic is captured at the ISP's point of view).
42      */
43     private final String mRouterWanIp;
44                 private static final String ROUTER_WAN_IP = "128.195.205.105";
45                 
46     public TcpReassembler() {
47         mRouterWanIp = ROUTER_WAN_IP;
48     }
49                 
50     public TcpReassembler(String routerWanIp) {
51         mRouterWanIp = routerWanIp;
52     }
53
54     @Override
55     public void gotPacket(PcapPacket pcapPacket) {
56         IpV4Packet ipPacket = pcapPacket.get(IpV4Packet.class);
57         TcpPacket tcpPacket = pcapPacket.get(TcpPacket.class);
58
59         if (ipPacket == null || tcpPacket == null) {
60             return;
61         }
62         // ... TODO?
63         processPacket(pcapPacket);
64 //        Class clazz = pcapPacket.getClass();
65 //        RadiotapPacket radiotapPacket = pcapPacket.get(RadiotapPacket.class);
66 //        Dot11ManagementPacket dot11ManagementPacket = pcapPacket.get(Dot11ManagementPacket.class);
67 //        if (dot11ManagementPacket != null) {
68 //            return;
69 //        }
70 //        if (radiotapPacket != null) {
71 //            processRadiotapPacket(pcapPacket);
72 //        }
73     }
74
75     /**
76      * Get the reassembled TCP connections. Note that if this is called while packets are still being processed (by
77      * calls to {@link #gotPacket(PcapPacket)}), the behavior is undefined and the returned list may be inconsistent.
78      * @return The reassembled TCP connections.
79      */
80     public List<Conversation> getTcpConversations() {
81         ArrayList<Conversation> combined = new ArrayList<>();
82         combined.addAll(mTerminatedConversations);
83         combined.addAll(mOpenConversations.values());
84         return combined;
85     }
86
87     private void processRadiotapPacket(PcapPacket pcapPacket) {
88         RadiotapPacket radiotapPacket = pcapPacket.get(RadiotapPacket.class);
89
90         RadiotapPacket.RadiotapHeader header = radiotapPacket.getHeader();
91         short length = header.getLength();
92         ArrayList<RadiotapPacket.RadiotapData> radiotapData = header.getDataFields();
93         // TODO: We can handle this 802.11 QoS data by creating our own class
94         // TODO: We only need to handle the first few bytes for source, destination, receiver, and transmitter
95         // TODO: addresses
96         Packet dataPacket = radiotapPacket.getPayload();
97         int dataLength = dataPacket.length();
98     }
99
100     private void processPacket(PcapPacket pcapPacket) {
101         TcpPacket tcpPacket = pcapPacket.get(TcpPacket.class);
102         // Handle client connection initiation attempts.
103         if (tcpPacket.getHeader().getSyn() && !tcpPacket.getHeader().getAck()) {
104             // A segment with the SYN flag set, but no ACK flag indicates that a client is attempting to initiate a new
105             // connection.
106             processNewConnectionRequest(pcapPacket);
107             return;
108         }
109         // Handle server connection initiation acknowledgement
110         if (tcpPacket.getHeader().getSyn() && tcpPacket.getHeader().getAck()) {
111             // A segment with both the SYN and ACK flags set indicates that the server has accepted the client's request
112             // to initiate a new connection.
113             processNewConnectionAck(pcapPacket);
114             return;
115         }
116         // Handle resets
117         if (tcpPacket.getHeader().getRst()) {
118             processRstPacket(pcapPacket);
119             return;
120         }
121         // Handle FINs
122         if (tcpPacket.getHeader().getFin()) {
123             // Handle FIN packet.
124             processFinPacket(pcapPacket);
125         }
126         // Handle ACKs (currently only ACKs of FINS)
127         if (tcpPacket.getHeader().getAck()) {
128             processAck(pcapPacket);
129         }
130         // Handle packets that carry payload (application data).
131         if (tcpPacket.getPayload() != null) {
132             processPayloadPacket(pcapPacket);
133         }
134     }
135
136     private void processNewConnectionRequest(PcapPacket clientSynPacket) {
137         // A SYN w/o ACK always originates from the client.
138         Conversation conv = Conversation.fromPcapPacket(clientSynPacket, true);
139         conv.addSynPacket(clientSynPacket);
140         // Is there an ongoing conversation for the same four tuple (clientIp, clientPort, serverIp, serverPort) as
141         // found in the new SYN packet?
142         Conversation ongoingConv = mOpenConversations.get(conv);
143         if (ongoingConv != null) {
144             if (ongoingConv.isRetransmission(clientSynPacket)) {
145                 // SYN retransmission detected, do nothing.
146                 return;
147                 // TODO: the way retransmission detection is implemented may cause a bug for connections where we have
148                 // not recorded the initial SYN, but only the SYN ACK, as retransmission is determined by comparing the
149                 // sequence numbers of initial SYNs -- and if no initial SYN is present for the Conversation, the new
150                 // SYN will be interpreted as a retransmission. Possible fix: let isRentransmission ALWAYS return false
151                 // when presented with a SYN packet when the Conversation already holds a SYN ACK packet?
152             } else {
153                 // New SYN has different sequence number than SYN recorded for ongoingConv, so this must be an attempt
154                 // to establish a new conversation with the same four tuple as ongoingConv.
155                 // Mark existing connection as terminated.
156                 // TODO: is this 100% theoretically correct, e.g., if many connection attempts are made back to back? And RST packets?
157                 mTerminatedConversations.add(ongoingConv);
158                 mOpenConversations.remove(ongoingConv);
159             }
160         }
161         // Finally, update the map of open connections with the new connection.
162         mOpenConversations.put(conv, conv);
163     }
164
165
166     /*
167      * TODO a problem across the board for all processXPacket methods below:
168      * if we start the capture in the middle of a TCP connection, we will not have an entry for the conversation in the
169      * map as we have not seen the initial SYN packet.
170      * Two ways we can address this:
171      * a) Perform null-checks and ignore packets for which we have not seen SYN
172      *    + easy to get correct
173      *    - we discard data (issue for long-lived connections!)
174      * b) Add a corresponding conversation entry whenever we encounter a packet that does not map to a conversation
175      *    + we consider all data
176      *    - not immediately clear if this will introduce bugs (incorrectly mapping packets to wrong conversations?)
177      *
178      *  [[[ I went with option b) for now; see getOngoingConversationOrCreateNew(PcapPacket pcapPacket). ]]]
179      */
180
181     private void processNewConnectionAck(PcapPacket srvSynPacket) {
182         // Find the corresponding ongoing connection, if any (if we start the capture just *after* the initial SYN, no
183         // ongoing conversation entry will exist, so it must be created in that case).
184 //        Conversation conv = mOpenConversations.get(Conversation.fromPcapPacket(srvSynPacket, false));
185         Conversation conv = getOngoingConversationOrCreateNew(srvSynPacket);
186         // Note: exploits &&'s short-circuit operation: only attempts to add non-retransmissions.
187         if (!conv.isRetransmission(srvSynPacket) && !conv.addSynPacket(srvSynPacket)) {
188             // For safety/debugging: if NOT a retransmission and add fails,
189             // something has gone terribly wrong/invariant is broken.
190 //            throw new AssertionError("Attempt to add SYN ACK packet that was NOT a retransmission failed." +
191 //                    Conversation.class.getSimpleName() + " invariant broken.");
192         }
193     }
194
195     private void processRstPacket(PcapPacket rstPacket) {
196         Conversation conv = getOngoingConversationOrCreateNew(rstPacket);
197         // Add RST packet to conversation.
198         conv.addRstPacket(rstPacket);
199         // Move conversation to set of terminated conversations.
200         mTerminatedConversations.add(conv);
201         mOpenConversations.remove(conv, conv);
202     }
203
204     private void processFinPacket(PcapPacket finPacket) {
205 //        getOngoingConversationForPacket(finPacket).addFinPacket(finPacket);
206         getOngoingConversationOrCreateNew(finPacket).addFinPacket(finPacket);
207     }
208
209     private void processAck(PcapPacket ackPacket) {
210 //        getOngoingConversationForPacket(ackPacket).attemptAcknowledgementOfFin(ackPacket);
211         // Note that unlike the style for SYN, FIN, and payload packets, for "ACK only" packets, we want to avoid
212         // creating a new conversation.
213         Conversation conv = getOngoingConversationForPacket(ackPacket);
214         if (conv != null) {
215             // The ACK may be an ACK of a FIN, so attempt to mark the FIN as ack'ed.
216             conv.attemptAcknowledgementOfFin(ackPacket);
217             if (conv.isGracefullyShutdown()) {
218                 // Move conversation to set of terminated conversations.
219                 mTerminatedConversations.add(conv);
220                 mOpenConversations.remove(conv);
221             }
222         }
223         // Note: add (additional) processing of ACKs (that are not ACKs of FINs) as necessary here...
224     }
225
226     private void processPayloadPacket(PcapPacket pcapPacket) {
227 //        getOngoingConversationForPacket(pcapPacket).addPacket(pcapPacket, true);
228         getOngoingConversationOrCreateNew(pcapPacket).addPacket(pcapPacket, true);
229     }
230
231     /**
232      * Locates an ongoing conversation (if any) that {@code pcapPacket} pertains to.
233      * @param pcapPacket The packet that is to be mapped to an ongoing {@code Conversation}.
234      * @return The {@code Conversation} matching {@code pcapPacket} or {@code null} if there is no match.
235      */
236     private Conversation getOngoingConversationForPacket(PcapPacket pcapPacket) {
237         // We cannot know if this is a client-to-server or server-to-client packet without trying both options...
238         Conversation conv = mOpenConversations.get(Conversation.fromPcapPacket(pcapPacket, true));
239         if (conv == null) {
240             conv = mOpenConversations.get(Conversation.fromPcapPacket(pcapPacket, false));
241         }
242         return conv;
243     }
244
245     /**
246      * Like {@link #getOngoingConversationForPacket(PcapPacket)}, but creates and inserts a new {@code Conversation}
247      * into {@link #mOpenConversations} if no open conversation is found (i.e., in the case that
248      * {@link #getOngoingConversationForPacket(PcapPacket)} returns {@code null}).
249      *
250      * @param pcapPacket The packet that is to be mapped to an ongoing {@code Conversation}.
251      * @return The existing, ongoing {@code Conversation} matching {@code pcapPacket} or the newly created one in case
252      *         no match was found.
253      */
254     private Conversation getOngoingConversationOrCreateNew(PcapPacket pcapPacket) {
255         Conversation conv = getOngoingConversationForPacket(pcapPacket);
256         if (conv == null) {
257             TcpPacket tcpPacket = pcapPacket.get(TcpPacket.class);
258             if (tcpPacket.getHeader().getSyn() && tcpPacket.getHeader().getAck()) {
259                 // A SYN ACK packet always originates from the server (it is a reply to the initial SYN packet from the client)
260                 conv = Conversation.fromPcapPacket(pcapPacket, false);
261             } else {
262                 // TODO: can we do anything else but arbitrarily select who is designated as the server in this case?
263                 // We can check if the IP prefix matches a local IP when handling traffic observed inside the local
264                 // network, but that obviously won't be a useful strategy for an observer at the WAN port.
265                 String srcIp = pcapPacket.get(IpV4Packet.class).getHeader().getSrcAddr().getHostAddress();
266                 // TODO: REPLACE THE ROUTER'S IP WITH A PARAMETER!!!
267                 boolean clientIsSrc = srcIp.startsWith("10.") || srcIp.startsWith("192.168.") || srcIp.equals(mRouterWanIp);
268                 conv = Conversation.fromPcapPacket(pcapPacket, clientIsSrc);
269             }
270             mOpenConversations.put(conv, conv);
271         }
272         return conv;
273     }
274 }