-
Notifications
You must be signed in to change notification settings - Fork 1
/
tcpstats.py
executable file
·299 lines (228 loc) · 9.61 KB
/
tcpstats.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Name of the project TcpStats
Author Ladislav Sulak
Login xsulak04
Date 4/2016
File tcpstats.py
Description Project in Python for subject PDS (Data Communications, Computer Networks and Protocols)
The aim of this project is to display some statistical data from the input provided
from pcap-like file.
The solution operates in these steps:
* firstly, there is a script written in Python. It requires 1 argument,
which is the name of the input pcap-like file. As a result,
it outputs a log file in Json format, so the result statistical
data are saved in Json representation. For parsing the input pcap file I used a
Scapy library,
* secondly, a data from such Json file are displayed in charts on HTML page.
It uses Javascript and CSS languages for this purpose. To be more specific,
it operates with JQuery, Bootstrap and Highcharts libraries.
Metro Dashboard free CSS template has been used for a design part.
"""
import os
import sys
import json
import scapy.all as scapy
from scapy.all import Ether,IP, TCP,Raw
import time
import datetime
from datetime import date
class EndPoint:
"""
This class represends 1 endpoint in network. After initialization we know its MAC/IP adresses and used port
"""
def __init__(self, _srcMac, _dstMac, _srcIP, _dstIP, _srcPort, _dstPort):
self.srcMac = _srcMac
self.dstMac = _dstMac
self.srcIP = _srcIP
self.dstIP = _dstIP
self.srcPort = _srcPort
self.dstPort = _dstPort
self.wndScale = 1
self.startWndSize = 0
self.wasFin = False
self.wasHandshake = False
self.allPkts = [] # List of object Packet
self.pktAmount = 0
self.startComm = None
self.endComm = None
def show(self):
""" For debugging purposes - print info about endpoint """
print("------- Endpoint Info -------\n" + \
"Start => " + self.startComm + "\nEnd: => " + self.endComm + \
"\n[srcMac] => " + self.srcMac + "\n[dstMac] => " + self.dstMac + \
"\n[srcIP] => " + self.srcIP + "\n[dstIP] => " + self.dstIP + \
"\n[Wnd scale] => " + str(self.wndScale) + \
"\n[wasFin] => " + ( "YES" if self.wasFin else "NO") + \
"\n[PktAmount] => " + (str(self.pktAmount)) + \
"\n[srcPort] => " + (str(self.srcPort)) + \
"\n[dstPort] => " + (str(self.dstPort)) + \
"\n------- Packets -------")
map(sys.stdout.write, (str(pkt) for pkt in self.allPkts))
print("\n\n")
def computeStartEndTime(self):
""" Time boundaries of communication """
firstPktTime = self.allPkts[0].timestamp
lastPktTime = self.allPkts[len(self.allPkts)-1].timestamp
self.startComm = datetime.datetime.fromtimestamp(firstPktTime).strftime("%Y-%m-%d %H:%M:%S.%f")
self.endComm = datetime.datetime.fromtimestamp(lastPktTime).strftime("%Y-%m-%d %H:%M:%S.%f")
def computeWindowSizes(self):
""" Window sizes in time. Everything is already prepared from parsing stage. """
startEpochTime = self.allPkts[0].timestamp
currentWndSize = self.startWndSize * self.wndScale
allWindowSizes = []
for pkt in self.allPkts:
if pkt.isSyn:
continue
elif pkt.isAck:
currentWndSize = pkt.wndSize * self.wndScale # Shift is basically multiplication (wndScale is powered)
elif not ptk.isFin:
pass
else:
continue
allWindowSizes.append([round(pkt.timestamp - startEpochTime, 5), currentWndSize])
return allWindowSizes
def computeThroughput(self):
""" Unfortunetaly not completed - just print packet sizes in their timestamps... """
startEpochTime = self.allPkts[0].timestamp
throughput = []
for pkt in self.allPkts:
throughput.append([round(pkt.timestamp - startEpochTime, 5), pkt.size])
return throughput
def computeRTT(self, receivPkts):
"""
Iterating through data packets and searching for its ACK from opposite side.
ACK = SEQ + data size
"""
rttValues = []
for pkt in self.allPkts:
if pkt.dataLen > 0:
for opPkt in receivPkts:
if (opPkt.tcpAck == (pkt.tcpSeq + pkt.dataLen - 1) and opPkt.isAck):
rttValues.append([pkt.tcpSeq, 1000*(opPkt.timestamp - pkt.timestamp)])
break
return rttValues
def computeSeqNumbers(self):
""" Calculate relative sequential numbers for each endpoint. """
startEpochTime = self.allPkts[0].timestamp
allSeqNumbers = []
for pkt in self.allPkts:
if pkt.isSyn or pkt.isAck or pkt.isFin:
allSeqNumbers.append([round(pkt.timestamp - startEpochTime, 5), pkt.tcpSeq])
else:
continue
return allSeqNumbers
class Packet:
""" Class for packet representation - prepare its data and then better examination """
def __init__(self, _id, _timestamp, _isSyn, _isAck, _isFin, _tcpSeq, _tcpAck, _wndSize, _size, _dataLen):
self.id = _id
self.timestamp = _timestamp # Epoch time - according to WireShark
self.isSyn = _isSyn
self.isAck = _isAck
self.isFin = _isFin
self.tcpSeq = _tcpSeq
self.tcpAck = _tcpAck
self.wndSize = _wndSize
self.size = _size
self.dataLen = _dataLen
def __str__(self):
""" For printing Packet class, you just have to print(str(pkt)) if pkt is the instance of Packet. """
return "[" + str(self.id) + "] {:.2f}".format(self.timestamp) + "s" \
", [SYN|ACK|FIN]: " + ( "YES|" if self.isSyn else "NO|") + ( "YES|" if self.isAck else "NO|") + \
( "YES" if self.isFin else "NO") + \
", [SeqN]: " + str(self.tcpSeq) + ", [AckN]: " + str(self.tcpAck) + \
", [WndSz]: " + str(self.wndSize) + "\n"
def pcapParser(packets):
""" Function fills Endpoint classes from the input Pcap-like file. """
endPointA = None
endPointB = None
pktId = 1
firstPktA = True
firstPktB = True
for pkt in packets:
# Just for the first packet - save adresses and ports of each of 2 endpoints via instantiation of its object
endPointA = EndPoint(pkt[Ether].src, pkt.dst, pkt[IP].src, pkt[IP].dst, pkt[TCP].sport, pkt[TCP].dport)
endPointB = EndPoint(pkt[Ether].dst, pkt.src, pkt[IP].dst, pkt[IP].src, pkt[TCP].dport, pkt[TCP].sport)
break
# Save all necessary information about each packet to endpoint.allPkts list.
for pkt in packets:
isSyn = False
isAck = False
wasFin = False
if (pkt[TCP].flags & 1) > 0:
wasFin = True
if (pkt[TCP].flags & 2) > 0:
isSyn = True
if (pkt[TCP].flags & 16) > 0:
isAck = True
if endPointA.srcMac == pkt[Ether].src:
if isSyn:
options = pkt[TCP].options
options = dict(options)
endPointA.wndScale = 2**options['WScale'] # Compute Window scale
if firstPktA and (isSyn or isAck):
firstPktA = False
relativeSeqValA = pkt[TCP].seq # Initial Seq/Ack numbers,
relativeAckValA = pkt[TCP].ack # important for calculation of relative Seq/Ack numbers
processedPkt = Packet(pktId, pkt.time, isSyn, isAck, wasFin, pkt[TCP].seq - relativeSeqValA, pkt[TCP].ack - relativeAckValA, pkt[TCP].window, len(pkt), len(pkt[TCP].payload))
endPointA.allPkts.append(processedPkt)
elif endPointB.srcMac == pkt[Ether].src:
if isSyn:
options = pkt[TCP].options
options = dict(options)
endPointB.wndScale = 2**options['WScale'] # Compute Window scale
if firstPktB and (isSyn or isAck):
firstPktB = False
relativeSeqValB = pkt[TCP].seq # Initial Seq/Ack numbers,
relativeAckValB = pkt[TCP].ack # important for calculation of relative Seq/Ack numbers
processedPkt = Packet(pktId, pkt.time, isSyn, isAck, wasFin, pkt[TCP].seq - relativeSeqValB, pkt[TCP].ack - relativeAckValB, pkt[TCP].window, len(pkt), len(pkt[TCP].payload))
endPointB.allPkts.append(processedPkt)
if isSyn and isAck and wasFin:
endPointA.wasHandshake = True # Information if the handshake was presented
endPointB.wasHandshake = True
pktId += 1 # Id of packet - counter, like in wireshark. For better debugging
return (endPointA, endPointB)
def main(argv):
try:
if(len(argv) != 1):
print("Please provide only 1 input argument, the name of the input pcap file.")
return 1
# Proceed the input pcap file and save all packets and information about endpoints
# to two objects.
packets = scapy.sniff(offline=argv[0], filter="tcp")
(endPointA, endPointB) = pcapParser(packets)
endPointA.computeStartEndTime()
endPointB.computeStartEndTime()
# Compute Receiver/Sender's Window Sizes
senderWndSizes = endPointA.computeWindowSizes()
receivWndSizes = endPointB.computeWindowSizes()
# Compute Relative Sequential numbers
senderSeqNum = endPointA.computeSeqNumbers()
receivSeqNum = endPointB.computeSeqNumbers()
# Compute Transfer speed (Bandwidth)
senderThroughput = endPointA.computeThroughput()
receivThroughput = endPointB.computeThroughput()
# Compute Round-trip time
senderRTT = endPointA.computeRTT(endPointB.allPkts)
receivRTT = endPointB.computeRTT(endPointA.allPkts)
#endPointA.show()
#endPointB.show()
# Save statistical data in Json representation.
jsonToOutput = {
'SendWindow' : [{'name': 'Window Size', 'showInLegend': False, "data": senderWndSizes}],
'RecWindow' : [{'name': 'Window Size', 'showInLegend': False, "data": receivWndSizes}],
'SendBandwith' : [{'name': 'Throughput', 'showInLegend': False, "data": senderThroughput}],
'RecBandwith' : [{'name': 'Throughput', 'showInLegend': False, "data": receivThroughput}],
'SendRTT' : [{'name': 'RTT', 'showInLegend': False, "data": senderRTT}],
'RecRTT' : [{'name': 'RTT', 'showInLegend': False, "data": receivRTT}],
'SendSeqNum' : [{'name': 'Seq num', 'showInLegend': False, "data": senderSeqNum}],
'RecSeqNum' : [{'name': 'Seq num', 'showInLegend': False, "data": receivSeqNum}]
}
with open('log/tcpLog.json', 'w') as outputFile:
json.dump(jsonToOutput, outputFile)
except Exception:
print("An error, probably during input/output file.")
exit(1)
if __name__ == "__main__":
main(sys.argv[1:])